Backend

Node.js

Referência completa do runtime Node.js — módulos, fs, http, process, streams, workers, Fastify, Prisma e ecossistema moderno

Módulos: CommonJS vs ESM, __dirname, require vs import

Node.js suporta dois sistemas de módulos. CommonJS (CJS) é o padrão histórico com require() síncrono. ESM (ECMAScript Modules) é o padrão moderno com import/export estático. Para ativar ESM, adicione "type": "module" no package.json ou use a extensão .mjs.

// ── CommonJS (CJS) ─────────────────────────────────────────────
const fs   = require("node:fs");
const path = require("node:path");
const { calcTax } = require("./utils");          // named import
const OrderService = require("./order-service"); // default import

module.exports = { doSomething };          // named export
module.exports = class OrderService {};    // default export

// __dirname e __filename disponíveis nativamente
console.log(__dirname);   // diretório do arquivo atual
console.log(__filename);  // caminho completo do arquivo atual

// ── ESM ("type": "module" no package.json) ──────────────────────
import fs   from "node:fs/promises";
import path from "node:path";
import { calcTax } from "./utils.js";           // SEMPRE .js em imports relativos
import OrderService from "./order-service.js";

export const TAX_RATE = 0.1;
export function calcTax(v) { return v * TAX_RATE; }
export default class OrderService {}

// __dirname NÃO existe em ESM — equivalente:
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname  = path.dirname(__filename);

// import.meta.dirname (Node.js 22+) — mais simples
const dir = import.meta.dirname;

// Dynamic import — lazy loading ou import condicional
const { parse } = await import("csv-parse/sync");
const logger = await import(
  process.env.NODE_ENV === "production" ? "./logger.prod.js" : "./logger.dev.js"
);

// Interoperabilidade CJS ↔ ESM
// Node.js 22+: require() pode importar ESM sem flag
// ESM pode importar CJS: import cjsMod from "./old-module.cjs"
// CJS não pode importar ESM com require() (exceto Node.js 22+)

File System: fs/promises, path, watch, operações comuns

O módulo fs/promises é a forma moderna de trabalhar com arquivos — todas as operações retornam Promises, eliminando callbacks. path é essencial para manipular caminhos de forma portável entre OS.

import fs   from "node:fs/promises";
import path from "node:path";

// ── Leitura ─────────────────────────────────────────────────────
const content = await fs.readFile("data.txt", "utf-8");         // string
const buffer  = await fs.readFile("image.png");                 // Buffer
const json    = JSON.parse(await fs.readFile("config.json", "utf-8"));

// ── Escrita ─────────────────────────────────────────────────────
await fs.writeFile("output.txt", "conteúdo", "utf-8");          // substitui
await fs.appendFile("log.txt", `${new Date().toISOString()}\n`); // append

// ── Diretórios ──────────────────────────────────────────────────
await fs.mkdir("dist/assets", { recursive: true }); // cria hierarquia
const entries = await fs.readdir("src");            // string[]
const details = await fs.readdir("src", { withFileTypes: true }); // Dirent[]
details.filter(d => d.isDirectory()).map(d => d.name);

// ── Metadados ───────────────────────────────────────────────────
const stat = await fs.stat("arquivo.txt");
console.log(stat.size, stat.mtime, stat.isFile(), stat.isDirectory());

// Verificar existência (sem lançar exceção)
async function exists(p) {
  try { await fs.access(p); return true; }
  catch { return false; }
}

// ── Copiar / Mover / Deletar ────────────────────────────────────
await fs.copyFile("src.txt", "dst.txt");
await fs.rename("old.txt", "new.txt");          // também move entre dirs
await fs.rm("dir", { recursive: true, force: true }); // equivalente a rm -rf
await fs.unlink("arquivo.txt");                 // remove arquivo

// ── Path — manipulação portável ────────────────────────────────
path.join("src", "utils", "index.js");          // src/utils/index.js
path.resolve("src", "index.js");                // /abs/path/src/index.js
path.dirname("/src/utils/index.js");            // /src/utils
path.basename("/src/utils/index.js");           // index.js
path.basename("/src/utils/index.js", ".js");    // index
path.extname("/src/utils/index.js");            // .js
path.parse("/src/utils/index.js");
// { root: '/', dir: '/src/utils', base: 'index.js', ext: '.js', name: 'index' }

// ── Watch — monitorar mudanças ──────────────────────────────────
const watcher = fs.watch("src", { recursive: true });
for await (const { eventType, filename } of watcher) {
  if (filename) console.log(`${eventType}: ${filename}`);
}

// ── Streams para arquivos grandes ──────────────────────────────
import { createReadStream, createWriteStream } from "node:fs";
const readStream  = createReadStream("grande.csv", { encoding: "utf-8", highWaterMark: 64 * 1024 });
const writeStream = createWriteStream("output.txt");
readStream.pipe(writeStream);

HTTP nativo e fetch: servidor, cliente, Node.js 18+

node:http permite criar servidores HTTP sem dependências externas. O fetch nativo (estável desde Node.js 18) substitui bibliotecas como axios e node-fetch para requisições HTTP.

import http  from "node:http";
import https from "node:https";

// ── Servidor HTTP nativo ────────────────────────────────────────
const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  if (req.method === "GET" && url.pathname === "/health") {
    res.writeHead(200, { "Content-Type": "application/json" });
    return res.end(JSON.stringify({ status: "ok" }));
  }

  if (req.method === "POST" && url.pathname === "/data") {
    // Ler body
    const chunks = [];
    for await (const chunk of req) chunks.push(chunk);
    const body = JSON.parse(Buffer.concat(chunks).toString());
    res.writeHead(201, { "Content-Type": "application/json" });
    return res.end(JSON.stringify({ received: body }));
  }

  res.writeHead(404).end("Not Found");
});

server.listen(3000, "0.0.0.0", () => console.log("Servidor em :3000"));

// Graceful shutdown
process.on("SIGTERM", () => {
  server.close(() => {
    console.log("Servidor encerrado");
    process.exit(0);
  });
});

// ── fetch nativo (Node.js 18+) ─────────────────────────────────
// GET
const res  = await fetch("https://api.exemplo.com/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

// POST com JSON
const resp = await fetch("https://api.exemplo.com/orders", {
  method:  "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body:    JSON.stringify({ customerId: "cst-1", items: ["prd-1"] }),
});

// Timeout com AbortController
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
  const res = await fetch("https://api.lenta.com/data", { signal: controller.signal });
  const json = await res.json();
  clearTimeout(timeout);
  return json;
} catch (err) {
  if (err.name === "AbortError") throw new Error("Timeout de 5s excedido");
  throw err;
}

// Stream de resposta grande (sem carregar tudo na memória)
const fileRes = await fetch("https://cdn.exemplo.com/arquivo-grande.zip");
const dest    = createWriteStream("download.zip");
await pipeline(fileRes.body, dest);

process: argv, env, exit, sinais, cwd, hrtime

O objeto global process fornece informações e controle sobre o processo Node.js em execução — sem necessidade de import.

// ── Argumentos e ambiente ───────────────────────────────────────
process.argv;          // ['node', 'script.js', '--port', '3000', ...]
process.argv.slice(2); // argumentos do usuário: ['--port', '3000', ...]
process.env.NODE_ENV;  // "development" | "production" | "test" | undefined
process.env.PORT ?? "3000";

// Definir variáveis de ambiente antes de iniciar
// NODE_ENV=production PORT=8080 node server.js

// ── Informações do processo ─────────────────────────────────────
process.pid;           // ID do processo
process.ppid;          // ID do processo pai
process.cwd();         // diretório de trabalho atual
process.chdir("/tmp"); // mudar diretório de trabalho
process.platform;      // "linux" | "darwin" | "win32"
process.arch;          // "x64" | "arm64"
process.version;       // "v22.0.0"
process.versions.v8;   // versão do motor V8

// ── I/O padrão ─────────────────────────────────────────────────
process.stdout.write("sem newline");
process.stderr.write("erro\n");
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (line) => console.log("Recebido:", line.trim()));

// ── Encerramento ───────────────────────────────────────────────
process.exit(0);   // encerrar com sucesso
process.exit(1);   // encerrar com erro

// ── Sinais do sistema ──────────────────────────────────────────
process.on("SIGTERM", () => { gracefulShutdown(); });   // kill / docker stop
process.on("SIGINT",  () => { gracefulShutdown(); });   // Ctrl+C
process.on("SIGHUP",  () => { reloadConfig(); });       // reload

// ── Erros não tratados ─────────────────────────────────────────
process.on("uncaughtException", (err) => {
  console.error("Exceção não capturada:", err);
  process.exit(1); // SEMPRE encerrar após uncaughtException
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("Promise não tratada:", reason);
  process.exit(1);
});

// ── Medição de performance ─────────────────────────────────────
const start = process.hrtime.bigint();       // nanosegundos (BigInt)
await doHeavyWork();
const elapsed = process.hrtime.bigint() - start;
console.log(`${elapsed / 1_000_000n}ms`);   // converter para ms

// Memória
const mem = process.memoryUsage();
console.log(`RSS: ${Math.round(mem.rss / 1024 / 1024)}MB`);
console.log(`Heap usado: ${Math.round(mem.heapUsed / 1024 / 1024)}MB`);

child_process: exec, spawn, fork, execFile

child_process executa comandos externos ou outros processos Node.js. Use spawn para processos longos com streaming; exec para comandos simples que retornam output; fork para workers Node.js com comunicação via IPC.

import { exec, spawn, fork, execFile } from "node:child_process";
import { promisify } from "node:util";

const execAsync = promisify(exec);

// ── exec — comando simples, output completo ────────────────────
const { stdout, stderr } = await execAsync("git log --oneline -5");
console.log(stdout);

// CUIDADO: exec usa shell — não interpole variáveis externas (injection!)
// Ruim:  exec(`ls ${userInput}`)
// Bom:   execFile("ls", [userInput])

// ── execFile — mais seguro, sem shell ─────────────────────────
const execFileAsync = promisify(execFile);
const { stdout: out } = await execFileAsync("git", ["log", "--oneline", "-5"]);

// ── spawn — streaming, processos longos ───────────────────────
const child = spawn("npm", ["run", "build"], {
  cwd:   "./frontend",
  stdio: ["inherit", "pipe", "pipe"], // stdin herdado, stdout/stderr capturados
});

child.stdout.on("data", (data) => process.stdout.write(data));
child.stderr.on("data", (data) => process.stderr.write(data));

const exitCode = await new Promise((resolve, reject) => {
  child.on("close", resolve);
  child.on("error", reject);
});

if (exitCode !== 0) throw new Error(`Build falhou com código ${exitCode}`);

// spawn com output herdado diretamente (simples)
spawn("docker", ["compose", "up", "-d"], { stdio: "inherit" });

// ── fork — processo Node.js filho com canal IPC ─────────────────
const worker = fork("./worker.js", [], { silent: false });

// Enviar mensagem ao filho
worker.send({ type: "process", data: largeDataset });

// Receber do filho
worker.on("message", (msg) => {
  if (msg.type === "done") console.log("Resultado:", msg.result);
});

// worker.js
process.on("message", async (msg) => {
  if (msg.type === "process") {
    const result = await heavyComputation(msg.data);
    process.send({ type: "done", result });
  }
});

crypto: hash, HMAC, cifração, geração de tokens

O módulo node:crypto fornece funções criptográficas sem dependências externas. Use-o para hashes, HMACs, cifragem simétrica e geração de tokens seguros.

import crypto from "node:crypto";

// ── Hash ────────────────────────────────────────────────────────
const hash = crypto.createHash("sha256").update("texto").digest("hex");

// Hash de arquivo grande (sem carregar na memória)
import { createReadStream } from "node:fs";
async function hashFile(path) {
  const hash = crypto.createHash("sha256");
  const stream = createReadStream(path);
  for await (const chunk of stream) hash.update(chunk);
  return hash.digest("hex");
}

// ── HMAC — integridade com chave secreta ────────────────────────
const hmac = crypto.createHmac("sha256", process.env.HMAC_SECRET)
  .update(JSON.stringify(payload))
  .digest("hex");

// Verificar HMAC de forma segura (evita timing attacks)
const expected = crypto.createHmac("sha256", secret).update(data).digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));

// ── Tokens seguros ─────────────────────────────────────────────
const token     = crypto.randomBytes(32).toString("hex");       // 64 chars hex
const urlToken  = crypto.randomBytes(24).toString("base64url"); // URL-safe
const uuid      = crypto.randomUUID();                          // UUID v4

// ── Cifragem simétrica (AES-256-GCM) ───────────────────────────
function encrypt(text, key) {
  const iv         = crypto.randomBytes(12);
  const cipher     = crypto.createCipheriv("aes-256-gcm", key, iv);
  const encrypted  = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
  const authTag    = cipher.getAuthTag();
  return { iv: iv.toString("hex"), tag: authTag.toString("hex"), data: encrypted.toString("hex") };
}

function decrypt({ iv, tag, data }, key) {
  const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "hex"));
  decipher.setAuthTag(Buffer.from(tag, "hex"));
  return Buffer.concat([decipher.update(Buffer.from(data, "hex")), decipher.final()]).toString("utf8");
}

// Gerar chave AES-256 a partir de senha (PBKDF2)
const key = crypto.pbkdf2Sync(password, salt, 100_000, 32, "sha256");

Event Loop: call stack, microtasks, nextTick vs setImmediate

O Event Loop do Node.js processa operações assíncronas em fases bem definidas. Entender a ordem de execução evita bugs sutis em código que mistura I/O, timers e Promises — especialmente em testes e em código de infraestrutura.

Fases do Event Loop (simplificado):
1. Timers (setTimeout, setInterval)
2. Pending callbacks (erros de TCP, etc.)
3. Poll (I/O: fs.read, network)
4. Check (setImmediate)
5. Close callbacks

Antes de cada fase: microtask queue (Promise.then, queueMicrotask)
Antes das microtasks: process.nextTick queue
// Demonstração de ordem de execução
console.log("1 - síncrono início");

setTimeout(()         => console.log("5 - macrotask setTimeout(0)"), 0);
setImmediate(()       => console.log("4 - setImmediate"));
process.nextTick(()   => console.log("2 - nextTick"));
Promise.resolve().then(() => console.log("3 - microtask Promise"));

console.log("6 - síncrono fim"); // ERRADO! síncrono fim executa antes das async

// Saída CORRETA:
// 1 - síncrono início
// 6 - síncrono fim        (call stack ainda não vazia)
// 2 - nextTick            (nextTick tem prioridade sobre microtasks)
// 3 - microtask Promise
// 4 - setImmediate        (dentro de I/O callback, roda antes de setTimeout)
// 5 - macrotask setTimeout

// Quando usar nextTick vs setImmediate:
// nextTick  → adie para DEPOIS da operação atual mas ANTES de qualquer I/O
//             uso: emitir eventos após construtor
// setImmediate → adie para PRÓXIMA iteração (após I/O)
//             uso: ceder CPU ao I/O entre lotes de processamento

// Emitir evento após construtor (padrão correto)
import { EventEmitter } from "node:events";
class OrderService extends EventEmitter {
  constructor(id) {
    super();
    process.nextTick(() => this.emit("ready", id)); // garante que .on() foi registrado
  }
}

EventEmitter: padrões, eventos tipados, memory leaks, once vs on

EventEmitter é a base de toda I/O do Node.js. No TypeScript 22+, é possível passar um mapa de eventos como generic para ter tipagem completa de nomes e argumentos.

import { EventEmitter } from "node:events";

class OrderBus extends EventEmitter {}

const bus = new OrderBus();

// on — escuta todas as ocorrências (persiste)
bus.on("order:placed", (orderId, total) => {
  console.log(`Pedido ${orderId}: R$${total.toFixed(2)}`);
});

// once — executa apenas na primeira emissão, depois remove o listener
bus.once("order:paid", (orderId, paymentId) => {
  sendConfirmationEmail(orderId, paymentId);
});

// Remover listener específico
const auditHandler = (id, total) => auditLog(id, total);
bus.on("order:placed", auditHandler);
bus.off("order:placed", auditHandler); // remove apenas este handler

// Memory leak — Node emite warning se > 10 listeners no mesmo evento
bus.setMaxListeners(25); // aumentar se necessário
bus.getMaxListeners();   // inspecionar o limite atual

// prependListener — adiciona no início da fila (executa primeiro)
bus.prependListener("order:placed", (id, total) => logger.info({ id, total }));

// Listar todos os listeners de um evento
console.log(bus.listenerCount("order:placed")); // número
console.log(bus.listeners("order:placed"));     // array de funções

// Capturar erro (importante: evento "error" sem listener termina o processo)
bus.on("error", (err) => {
  logger.error(err, "Erro no OrderBus");
});

bus.emit("order:placed", "ord-123", 199.90);

// EventEmitter como Promise (esperar próximo evento)
import { once } from "node:events";
const [orderId, total] = await once(bus, "order:placed");

Streams: Readable, Writable, Transform, pipeline, backpressure

Streams processam dados em chunks sem carregar tudo na memória — essenciais para arquivos grandes, respostas HTTP em streaming e pipelines ETL. pipeline gerencia o ciclo de vida e faz cleanup em erros.

import { Readable, Writable, Transform, pipeline } from "node:stream";
import { promisify } from "node:util";
import { createReadStream, createWriteStream } from "node:fs";
import { createGzip } from "node:zlib";

const pipelineAsync = promisify(pipeline);

// Pipeline: lê CSV → comprime → salva (com cleanup automático em erro)
await pipelineAsync(
  createReadStream("pedidos.csv"),
  createGzip(),
  createWriteStream("pedidos.csv.gz")
);

// Transform stream customizado — converte CSV para objetos JSON
class CsvToJson extends Transform {
  constructor() { super({ readableObjectMode: true }); }

  _transform(chunk, _enc, done) {
    const lines = chunk.toString().split("\n").filter(Boolean);
    for (const line of lines) {
      const cols = line.split(",");
      if (!this.headers) { this.headers = cols; continue; }
      const obj = Object.fromEntries(this.headers.map((h, i) => [h, cols[i]]));
      this.push(obj);
    }
    done();
  }
}

// Readable.from — cria stream de iterável ou async generator (sem acumular tudo)
async function* generateOrders() {
  for (let i = 0; i < 1_000_000; i++) {
    yield { id: `ord-${i}`, total: Math.random() * 500 };
  }
}
const orderStream = Readable.from(generateOrders(), { objectMode: true });

// Backpressure — respeitar o retorno de .write()
function pump(readable, writable) {
  return new Promise((resolve, reject) => {
    readable.on("data", (chunk) => {
      if (!writable.write(chunk)) {
        readable.pause();
        writable.once("drain", () => readable.resume());
      }
    });
    readable.on("end",   resolve);
    readable.on("error", reject);
  });
}

// Async iteration — mais simples para consumir streams
for await (const record of orderStream) {
  await saveRecord(record);
}

Worker Threads: quando usar vs child_process, SharedArrayBuffer, Atomics

Worker Threads executam JavaScript em threads separadas dentro do mesmo processo, compartilhando memória via SharedArrayBuffer. Use para tarefas CPU-intensivas que bloqueariam o Event Loop. Para processos verdadeiramente independentes, child_process é mais adequado.

import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";

// === Arquivo: hash-worker.js ===
if (!isMainThread) {
  const { data } = workerData;
  const result   = computeExpensiveHash(data); // não bloqueia main thread
  parentPort?.postMessage({ hash: result });
  process.exit(0);
}

// === Main thread ===
function runInWorker(file, data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(new URL(file, import.meta.url), { workerData: data });
    worker.on("message", resolve);
    worker.on("error",   reject);
    worker.on("exit",    code => {
      if (code !== 0) reject(new Error(`Worker falhou com código ${code}`));
    });
  });
}

const { hash } = await runInWorker("./hash-worker.js", { data: "senha" });

// SharedArrayBuffer + Atomics — memória compartilhada entre threads
const sharedBuf  = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4);
const sharedArr  = new Int32Array(sharedBuf);

// Thread worker: aguarda notificação (bloqueia a thread worker, não o Event Loop)
Atomics.wait(sharedArr, 0, 0);      // bloqueia até sharedArr[0] !== 0
console.log("Notificado:", Atomics.load(sharedArr, 1));

// Main thread: escreve e notifica
Atomics.store(sharedArr, 1, 42);
Atomics.store(sharedArr, 0, 1);
Atomics.notify(sharedArr, 0, 1);    // acorda 1 thread aguardando no índice 0

// Quando usar o quê:
// Worker Threads  → CPU-bound (hash, compressão, parse pesado, ML inference)
// child_process   → processos isolados, stdin/stdout pipe, linguagens diferentes
// cluster         → fork do processo HTTP para múltiplos CPUs (servidor web)

Fastify completo: setup, plugins, schemas, hooks, decorators

Fastify é o framework Node.js mais performático para APIs — cerca de 2× mais rápido que Express em benchmarks oficiais. A performance vem da serialização via JSON Schema (sem JSON.stringify genérico) e do ciclo de vida de hooks bem definido. Tudo no Fastify é plugin.

import Fastify from "fastify";

const app = Fastify({
  logger: { level: "info" },
  trustProxy: true,
});

// Plugin — escopo isolado com prefix
app.register(async function ordersPlugin(fastify) {

  fastify.post("/", {
    schema: {
      body: {
        type: "object",
        required: ["customerId", "items"],
        properties: {
          customerId: { type: "string" },
          items:      { type: "array", items: { type: "string" }, minItems: 1 },
        },
      },
      response: {
        201: {
          type: "object",
          properties: { id: { type: "string" }, status: { type: "string" } },
        },
      },
    },
  }, async (request, reply) => {
    const order = await orderService.create(request.body);
    return reply.code(201).send(order);
  });

  fastify.get("/:id", async (request, reply) => {
    const order = await orderService.findById(request.params.id);
    if (!order) return reply.code(404).send({ error: "Não encontrado" });
    return order;
  });

}, { prefix: "/api/orders" });

// Hooks globais — ciclo de vida
app.addHook("onRequest", async (request) => {
  request.log.info({ url: request.url, method: request.method });
});

app.addHook("preHandler", async (request, reply) => {
  // autenticação global — pode chamar reply.send() para encerrar
});

app.addHook("onSend", async (request, reply, payload) => {
  reply.header("X-Request-Id", request.id);
  return payload; // retornar payload (pode ser modificado)
});

app.addHook("onError", async (request, reply, error) => {
  request.log.error({ err: error });
});

// Decorators — estendem fastify, request e reply
app.decorate("db", prismaClient);
app.decorateRequest("currentUser", null);

// Error handler global
app.setErrorHandler((error, request, reply) => {
  const status = error.statusCode ?? 500;
  reply.code(status).send({ error: error.message, statusCode: status });
});

await app.listen({ port: 3000, host: "0.0.0.0" });

Express vs Fastify — comparativo de patterns

Express é o framework mais popular, maduro e com ecossistema vasto. Fastify é mais moderno, rápido e oferece TypeScript de primeira classe. Para APIs novas, Fastify é a escolha recomendada; para projetos legados, Express ainda é sólido.

// ── EXPRESS ──────────────────────────────────────────────────────
import express from "express";
const app = express();
app.use(express.json());

// Middleware: qualquer função (req, res, next) — acoplamento via next()
app.use((req, res, next) => {
  console.log(req.method, req.url);
  next();
});

app.post("/orders", async (req, res, next) => {
  try {
    const body  = createOrderSchema.parse(req.body);
    const order = await orderService.create(body);
    res.status(201).json(order);
  } catch (err) {
    next(err); // delega para error middleware
  }
});

// Error middleware: 4 parâmetros (distinção de middleware comum)
app.use((err, req, res, _next) => {
  res.status(500).json({ error: err.message });
});

// ── FASTIFY ──────────────────────────────────────────────────────
import Fastify from "fastify";
const fastify = Fastify();

// Hook (não middleware): ciclo de vida explícito
fastify.addHook("onRequest", async (request) => {
  fastify.log.info(`${request.method} ${request.url}`);
});

fastify.post("/orders", {
  schema: {
    body: { type: "object", required: ["customerId"], properties: { customerId: { type: "string" } } },
    response: { 201: { type: "object", properties: { id: { type: "string" } } } },
  },
}, async (request, reply) => {
  // body já validado pelo AJV (compilado, não interpretado em runtime)
  const order = await orderService.create(request.body);
  return reply.code(201).send(order); // serializado via JSON Schema — mais rápido
});

// Diferenças chave:
// | Aspecto            | Express        | Fastify                    |
// |--------------------|----------------|----------------------------|
// | Validação          | Manual         | JSON Schema (AJV)          |
// | Serialização       | JSON.stringify | Schema-based (2-3× mais rápido) |
// | TypeScript         | @types/express | Nativo                     |
// | Extensibilidade    | Middleware     | Plugins com encapsulamento |
// | Performance        | ~35k req/s     | ~76k req/s (benchmark oficial) |

Autenticação JWT com Fastify: login, middleware, refresh token

JWT (JSON Web Token) é o padrão para autenticação stateless em APIs REST. O access token tem vida curta (15 minutos); o refresh token tem vida longa (7 dias) e é rotacionado — ao usar, um novo é emitido e o anterior é invalidado.

import Fastify          from "fastify";
import fastifyJwt       from "@fastify/jwt";
import fastifyCookie    from "@fastify/cookie";
import bcrypt           from "bcryptjs";

const app = Fastify({ logger: true });

await app.register(fastifyJwt, { secret: process.env.JWT_SECRET });
await app.register(fastifyCookie, { secret: process.env.COOKIE_SECRET });

// Decorator de autenticação — reutilizável
app.decorate("authenticate", async (request, reply) => {
  try {
    await request.jwtVerify();
  } catch {
    reply.code(401).send({ error: "Token inválido ou expirado" });
  }
});

// Login
app.post("/auth/login", async (request, reply) => {
  const { email, password } = request.body;
  const user = await userRepo.findByEmail(email);
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return reply.code(401).send({ error: "Credenciais inválidas" });
  }

  const accessToken  = app.jwt.sign({ sub: user.id, role: user.role }, { expiresIn: "15m" });
  const refreshToken = app.jwt.sign({ sub: user.id, type: "refresh" }, { expiresIn: "7d" });

  // Salvar hash do refresh token no banco (rotação)
  await tokenRepo.save({ userId: user.id, tokenHash: await bcrypt.hash(refreshToken, 10) });

  return reply
    .setCookie("refresh_token", refreshToken, { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh" })
    .send({ accessToken });
});

// Rota protegida
app.get("/me", { preHandler: [app.authenticate] }, async (request) => {
  const payload = request.user;
  return userRepo.findById(payload.sub);
});

// Refresh token
app.post("/auth/refresh", async (request, reply) => {
  const refreshToken = request.cookies.refresh_token;
  if (!refreshToken) return reply.code(401).send({ error: "Refresh token ausente" });
  try {
    const payload = app.jwt.verify(refreshToken);
    const stored  = await tokenRepo.findByUserId(payload.sub);
    if (!stored || !(await bcrypt.compare(refreshToken, stored.tokenHash))) {
      return reply.code(401).send({ error: "Refresh token inválido" });
    }
    const newAccessToken = app.jwt.sign({ sub: payload.sub }, { expiresIn: "15m" });
    return reply.send({ accessToken: newAccessToken });
  } catch {
    return reply.code(401).send({ error: "Refresh token expirado" });
  }
});

Banco de dados com Prisma: schema, migrations, queries, transactions, nested writes

Prisma é o ORM TypeScript mais popular. O schema declarativo em schema.prisma gera o client completamente tipado automaticamente. Queries têm autocomplete completo — nomes de campos, filtros e operações são verificados em compile time.

// prisma/schema.prisma (comentado para caber aqui)
// datasource db { provider = "postgresql"; url = env("DATABASE_URL") }
// generator client { provider = "prisma-client-js" }
//
// model User {
//   id      String   @id @default(cuid())
//   email   String   @unique
//   name    String?
//   orders  Order[]
//   @@map("users")
// }
// model Order {
//   id        String      @id @default(cuid())
//   total     Decimal
//   status    OrderStatus @default(PENDING)
//   userId    String
//   user      User        @relation(fields: [userId], references: [id])
//   items     OrderItem[]
//   createdAt DateTime    @default(now())
//   @@map("orders")
// }
// enum OrderStatus { PENDING PAID CANCELLED }

import { PrismaClient, Prisma } from "@prisma/client";
const prisma = new PrismaClient({ log: ["query", "error"] });

// Queries básicas
const orders = await prisma.order.findMany({
  where:   { status: "PENDING", userId: "usr-123" },
  orderBy: { createdAt: "desc" },
  take: 10, skip: 0,
  include: { items: true, user: { select: { name: true, email: true } } },
});

// Upsert — cria ou atualiza
const user = await prisma.user.upsert({
  where:  { email: "ana@example.com" },
  update: { name: "Ana Silva" },
  create: { email: "ana@example.com", name: "Ana Silva" },
});

// Nested write — cria pedido com itens atomicamente
const order = await prisma.order.create({
  data: {
    userId: "usr-123",
    total:  new Prisma.Decimal(199.90),
    items:  {
      create: [
        { productId: "prd-1", qty: 2, price: new Prisma.Decimal(49.95) },
        { productId: "prd-2", qty: 1, price: new Prisma.Decimal(99.99) },
      ],
    },
  },
  include: { items: true },
});

// Transaction interativa — múltiplas operações atômicas
const result = await prisma.$transaction(async (tx) => {
  const updated = await tx.order.update({
    where: { id: "ord-123" },
    data:  { status: "PAID" },
  });
  await tx.payment.create({
    data: { orderId: updated.id, amount: updated.total, gateway: "stripe" },
  });
  return updated;
});

// Transaction sequencial (mais simples, menos controle)
await prisma.$transaction([
  prisma.order.update({ where: { id: "ord-1" }, data: { status: "PAID" } }),
  prisma.audit.create({ data: { action: "order_paid", orderId: "ord-1" } }),
]);

Variáveis de ambiente: Zod validation, dotenv, distinção dev/prod

Validar variáveis de ambiente na inicialização evita erros silenciosos em runtime. Se uma variável obrigatória estiver ausente, o processo termina imediatamente com uma mensagem clara — muito melhor do que falhar em alguma operação horas depois.

// src/config/env.js
import { z } from "zod";

const envSchema = z.object({
  NODE_ENV:     z.enum(["development", "production", "test"]).default("development"),
  PORT:         z.coerce.number().int().min(1).max(65535).default(3000),
  DATABASE_URL: z.string().url("DATABASE_URL deve ser uma URL válida"),
  JWT_SECRET:   z.string().min(32, "JWT_SECRET deve ter no mínimo 32 caracteres"),
  REDIS_URL:    z.string().url().optional(),
  LOG_LEVEL:    z.enum(["fatal","error","warn","info","debug","trace"]).default("info"),
  CORS_ORIGIN:  z.string().default("http://localhost:5173"),
});

const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error("ERRO: Variáveis de ambiente inválidas:");
  console.error(JSON.stringify(parsed.error.flatten().fieldErrors, null, 2));
  process.exit(1);
}

export const env = parsed.data;

// dotenv — carregue ANTES de importar este arquivo
// via CLI:  dotenv -e .env -- node src/index.js
// via código (entry point):
//   import "dotenv/config"  (ESM)
//   require("dotenv/config") (CJS)

// Estrutura de arquivos:
// .env              → valores locais  (no .gitignore)
// .env.example      → template sem valores reais (no git)
// .env.test         → valores para testes (no .gitignore)
// .env.production   → NUNCA commitar — use secrets do CI/CD

Testing: Vitest, supertest, mocking de módulos, coverage

Vitest é o test runner moderno para projetos Node/TypeScript — suporte nativo a ESM e TypeScript sem configuração extra, API compatível com Jest e execução paralela por padrão. Para testes de integração HTTP, use Fastify’s inject() ou supertest.

// vitest.config.js
import { defineConfig } from "vitest/config";
export default defineConfig({
  test: {
    globals:     true,
    environment: "node",
    coverage: {
      provider:   "v8",
      reporter:   ["text", "html", "lcov"],
      thresholds: { lines: 80, branches: 75, functions: 80 },
      exclude:    ["**/migrations/**", "**/*.config.*"],
    },
  },
});

// order.service.test.js
import { describe, it, expect, vi, beforeEach } from "vitest";
import { OrderService } from "./order.service.js";

// Mock de módulo inteiro
vi.mock("../repositories/order.repository.js", () => ({
  OrderRepository: vi.fn().mockImplementation(() => ({
    save:     vi.fn().mockResolvedValue({ id: "ord-123", status: "pending", total: 99.90 }),
    findById: vi.fn().mockResolvedValue(null),
  })),
}));

describe("OrderService", () => {
  let service;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new OrderService(new OrderRepository());
  });

  it("cria pedido com status pending", async () => {
    const order = await service.create({ customerId: "cst-1", items: ["prd-1"] });
    expect(order.status).toBe("pending");
    expect(order.id).toBeTruthy();
  });

  it("lança erro para cliente inválido", async () => {
    await expect(service.create({ customerId: "", items: [] }))
      .rejects.toThrow("Cliente inválido");
  });
});

// Teste de integração com Fastify inject (sem abrir porta)
import { buildApp } from "../src/app.js";

describe("Orders API", () => {
  const app = await buildApp();

  it("POST /api/orders → 201", async () => {
    const res = await app.inject({
      method:  "POST",
      url:     "/api/orders",
      headers: { authorization: `Bearer ${validToken}` },
      payload: { customerId: "cst-1", items: ["prd-1"] },
    });
    expect(res.statusCode).toBe(201);
    expect(res.json()).toMatchObject({ status: "pending" });
  });
});

Ferramentas modernas: tsx, tsup, pkgroll, npm workspaces

O ecossistema Node.js/TypeScript tem ferramentas especializadas para cada uso. tsx para execução direta em desenvolvimento; tsup para build de pacotes publicáveis; workspaces para monorepos.

# tsx — executa TypeScript direto sem etapa de compilação (usa esbuild internamente)
npx tsx src/index.ts
npx tsx watch src/index.ts          # restart automático ao salvar

# tsup — build otimizado para publicar pacotes npm
# gera CJS + ESM + .d.ts com uma linha
npx tsup src/index.ts --format cjs,esm --dts --clean
# ou via tsup.config.ts:
# import { defineConfig } from "tsup"
# export default defineConfig({ entry: ["src/index.ts"], format: ["cjs","esm"], dts: true, clean: true, splitting: true })

# package.json moderno para biblioteca dual-format
# {
#   "type": "module",
#   "exports": {
#     ".": {
#       "import":  "./dist/index.js",
#       "require": "./dist/index.cjs",
#       "types":   "./dist/index.d.ts"
#     }
#   },
#   "main":   "./dist/index.cjs",
#   "module": "./dist/index.js",
#   "types":  "./dist/index.d.ts"
# }

# npm workspaces — monorepo nativo (sem Lerna/Turborepo obrigatório)
# package.json raiz:  { "workspaces": ["packages/*", "apps/*"] }
npm install                           # instala todas as dependências (hoisting)
npm run build -w packages/core        # roda em workspace específico
npm run dev   -w apps/api
npm run test  -ws --if-present        # roda test em todos os workspaces que o têm

# Ferramentas adicionais úteis
npx tsc --noEmit                      # type-check sem gerar arquivos
npx eslint src --ext .ts              # linting
npx prettier --write src              # formatação
npx @biomejs/biome check src          # lint + format (alternativa moderna ao ESLint+Prettier)

Versões — Node.js 16 → 22 LTS

VersãoAnoStatusPrincipais novidades
16 LTS2021EOL (2023)V8 9.x; npm 8 com workspaces; Apple Silicon nativo; Promise-based APIs estáveis (fs/promises); --experimental-fetch
18 LTS2022Maintenance (até 2025)fetch nativo estável; Web Streams API; node:test runner embutido (experimental); V8 10.2; --experimental-vm-modules
20 LTS2023Active (até 2026)Permission model (--experimental-permission); node:test estável com --watch; V8 11.3; Ada URL parser; melhorias em ESM + CJS interop

Worker Threads

Worker Threads permitem executar JavaScript em paralelo dentro do mesmo processo Node.js, compartilhando memória via SharedArrayBuffer. Ideal para tarefas CPU-bound (compressão, criptografia, parsing pesado) — ao contrário de processos filhos, não há overhead de serialização para dados compartilhados.

Quando usar Worker Threads vs processos

CenárioUse
CPU-bound: compressão, hash, parsingWorker Threads
I/O-bound: banco, rede, arquivosEvent loop + async (sem workers)
Isolamento total, crash containmentchild_process / cluster
Código legado sem thread safetychild_process
// worker.js — código que roda na thread separada
import { workerData, parentPort } from "node:worker_threads";

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const result = fibonacci(workerData.n);
parentPort.postMessage({ result });

// main.js — thread principal
import { Worker } from "node:worker_threads";
import { fileURLToPath } from "node:url";
import path from "node:path";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

function runFibInWorker(n) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(path.join(__dirname, "worker.js"), {
      workerData: { n },
    });
    worker.on("message", (msg) => resolve(msg.result));
    worker.on("error", reject);
    worker.on("exit", (code) => {
      if (code !== 0) reject(new Error(`Worker encerrou com código ${code}`));
    });
  });
}

const result = await runFibInWorker(42);
console.log("Fibonacci(42):", result);

// ── SharedArrayBuffer + Atomics — memória compartilhada ─────────
// Útil para contadores, flags de sincronização entre workers
const sharedBuffer = new SharedArrayBuffer(4); // 4 bytes = 1 Int32
const counter      = new Int32Array(sharedBuffer);

// Em cada worker: incremento atômico (thread-safe)
Atomics.add(counter, 0, 1);
// Leitura atômica na thread principal
console.log("Counter:", Atomics.load(counter, 0));

// ── Pool de Workers — reusar threads em vez de criar/destruir ───
// Evita overhead de inicialização em requisições frequentes
class WorkerPool {
  #workers = [];
  #queue   = [];
  #size;

  constructor(workerFile, size = navigator.hardwareConcurrency ?? 4) {
    this.#size = size;
    for (let i = 0; i < size; i++) {
      this.#addWorker(workerFile);
    }
  }

  #addWorker(workerFile) {
    const worker = new Worker(workerFile);
    worker.busy = false;
    worker.on("message", (result) => {
      worker.busy = false;
      const { resolve } = worker.currentTask;
      resolve(result);
      this.#processQueue(workerFile);
    });
    this.#workers.push(worker);
  }

  #processQueue(workerFile) {
    if (this.#queue.length === 0) return;
    const freeWorker = this.#workers.find((w) => !w.busy);
    if (!freeWorker) return;
    const { data, resolve, reject } = this.#queue.shift();
    freeWorker.busy = true;
    freeWorker.currentTask = { resolve, reject };
    freeWorker.postMessage(data);
  }

  run(data) {
    return new Promise((resolve, reject) => {
      this.#queue.push({ data, resolve, reject });
      this.#processQueue();
    });
  }
}

const pool = new WorkerPool("./worker.js", 4);
const results = await Promise.all([
  pool.run({ n: 40 }),
  pool.run({ n: 41 }),
  pool.run({ n: 42 }),
]);

Cluster Module

O módulo cluster permite criar múltiplos processos Node.js que compartilham a mesma porta TCP, distribuindo conexões entre eles. Diferente de Worker Threads, cada processo tem seu próprio V8, heap e loop de eventos — ideal para escalar servidores HTTP em máquinas multi-core.

import cluster from "node:cluster";
import os from "node:os";
import http from "node:http";

const NUM_WORKERS = os.availableParallelism(); // Node 18+; fallback: os.cpus().length

if (cluster.isPrimary) {
  // ── Processo primário — gerencia workers ────────────────────────
  console.log(`Primary PID ${process.pid}; iniciando ${NUM_WORKERS} workers`);

  for (let i = 0; i < NUM_WORKERS; i++) {
    cluster.fork();
  }

  // Restart automático em caso de crash
  cluster.on("exit", (worker, code, signal) => {
    console.warn(`Worker ${worker.process.pid} morreu (${signal ?? code}). Reiniciando...`);
    cluster.fork();
  });

  // ── Comunicação primary → worker ────────────────────────────────
  // Enviar mensagem para todos os workers
  function broadcast(msg) {
    Object.values(cluster.workers).forEach((w) => w.send(msg));
  }

  // Receber mensagem de qualquer worker
  cluster.on("message", (worker, message) => {
    console.log(`Mensagem do worker ${worker.id}:`, message);
  });

  // ── Graceful shutdown do cluster ─────────────────────────────────
  process.on("SIGTERM", () => {
    console.log("SIGTERM recebido — encerrando workers...");
    for (const worker of Object.values(cluster.workers)) {
      worker.send({ type: "shutdown" });
      // Força após 10s se o worker não encerrar
      setTimeout(() => worker.kill(), 10_000).unref();
    }
  });

} else {
  // ── Worker — lógica da aplicação ────────────────────────────────
  const server = http.createServer((req, res) => {
    res.end(`Respondido pelo worker PID ${process.pid}`);
  });

  server.listen(3000, () => {
    console.log(`Worker ${process.pid} ouvindo na porta 3000`);
  });

  // Receber mensagem do primary
  process.on("message", (msg) => {
    if (msg?.type === "shutdown") {
      server.close(() => process.exit(0));
    }
  });

  // Enviar status ao primary
  setInterval(() => {
    process.send({ type: "stats", pid: process.pid, mem: process.memoryUsage().rss });
  }, 5000);
}

Child Processes

O módulo child_process permite executar processos externos — shell commands, binários ou outros scripts Node.js. Cada processo tem seu próprio PID, stdin/stdout/stderr e espaço de memória isolado.

Comparação das APIs

FunçãoShell?Streaming?Uso típico
exec()SimNão (buffer)Comandos curtos, saída pequena
execFile()NãoNão (buffer)Binário sem shell overhead
spawn()NãoSimProcessos longos, saída grande
fork()NãoVia IPCSubprocesso Node.js com canal de mensagens
import { exec, execFile, spawn, fork } from "node:child_process";
import { promisify } from "node:util";

const execAsync = promisify(exec);

// ── exec — comando simples, retorna buffer ───────────────────────
const { stdout, stderr } = await execAsync("git log --oneline -5");
console.log(stdout);
// Cuidado: exec usa shell — vulnerável a injeção se input vier do usuário

// ── execFile — sem shell, mais seguro ───────────────────────────
execFile("ffmpeg", ["-version"], (err, stdout) => {
  if (err) throw err;
  console.log(stdout);
});

// ── spawn — streaming stdout em tempo real ───────────────────────
const child = spawn("npm", ["run", "build"]);

child.stdout.on("data", (chunk) => process.stdout.write(chunk));
child.stderr.on("data", (chunk) => process.stderr.write(chunk));
child.on("close", (code) => console.log(`Build encerrou com código ${code}`));

// Abortar o processo filho com AbortController
const ac      = new AbortController();
const { signal } = ac;
const proc    = spawn("long-task", [], { signal });
setTimeout(() => ac.abort(), 5000); // cancela após 5s

// ── spawn com detached: true — processo independente ─────────────
// O processo filho continua rodando mesmo se o pai encerrar
const daemon = spawn("node", ["daemon.js"], {
  detached: true,
  stdio: "ignore", // fecha os pipes — pai não retém referência
});
daemon.unref(); // permite que o processo pai encerre livremente

// Matar processo filho explicitamente
const proc2 = spawn("some-server", []);
setTimeout(() => {
  proc2.kill("SIGTERM");                 // graceful
  setTimeout(() => proc2.kill("SIGKILL"), 3000); // força após 3s
}, 10_000);

// ── fork — subprocesso Node.js com canal IPC ─────────────────────
// worker-child.js:
// process.on("message", (msg) => { process.send({ result: msg.n * 2 }); });

const child2 = fork("./worker-child.js");
child2.send({ n: 21 });
child2.on("message", (msg) => {
  console.log("Resultado:", msg.result); // 42
  child2.kill();
});

Event Loop Internals

O event loop é o coração do Node.js. Ele executa em fases sequenciais, e entender essa ordem é essencial para prever o comportamento de código assíncrono.

Fases do Event Loop

   ┌────────────────────────────────────┐
   │           timers                   │  setTimeout, setInterval
   │  (executa callbacks vencidos)      │
   └────────────────┬───────────────────┘

   ┌────────────────▼───────────────────┐
   │       pending callbacks            │  erros de I/O do ciclo anterior
   └────────────────┬───────────────────┘

   ┌────────────────▼───────────────────┐
   │         idle, prepare              │  uso interno do Node
   └────────────────┬───────────────────┘

   ┌────────────────▼───────────────────┐
   │              poll                  │  busca novos eventos de I/O;
   │  (coração do loop)                 │  bloqueia aqui se queue vazia
   └────────────────┬───────────────────┘

   ┌────────────────▼───────────────────┐
   │              check                 │  setImmediate
   └────────────────┬───────────────────┘

   ┌────────────────▼───────────────────┐
   │          close callbacks           │  socket.on("close", ...)
   └────────────────────────────────────┘

Microtask queue (entre cada fase): process.nextTickPromise.then/catch/finallyqueueMicrotask

// ── Ordem de execução: exemplo prático ──────────────────────────
console.log("1 — síncrono");

setTimeout(() => console.log("5 — setTimeout(0)"), 0);   // fase timers

setImmediate(() => console.log("4 — setImmediate"));      // fase check

Promise.resolve().then(() => console.log("3 — Promise microtask"));

process.nextTick(() => console.log("2 — nextTick (microtask prioritária)"));

console.log("1b — ainda síncrono");
// Saída: 1 → 1b → 2 → 3 → 4 → 5

// ── Diferenças práticas ──────────────────────────────────────────
// process.nextTick  — executa ANTES de qualquer outra microtask;
//                     uso: garantir callback assíncrono após setup síncrono
// Promise.then      — executa após nextTick, antes de voltar ao loop
// setImmediate      — na fase "check", APÓS a fase poll (I/O); mais previsível
// setTimeout(fn, 0) — fase "timers"; pode ser DEPOIS de setImmediate se
//                     chamado fora de ciclo de I/O

// ── Como I/O non-blocking funciona ──────────────────────────────
// 1. fs.readFile() registra a operação no libuv (C++)
// 2. O event loop continua — executa outros callbacks
// 3. O SO completa a leitura e avisa o libuv
// 4. libuv coloca o callback na fila da fase "poll"
// 5. Na próxima iteração do loop, o callback é executado
// → O processo nunca "dorme" esperando I/O

// ── Bloqueio do event loop — o que evitar ───────────────────────
// Ruim: código síncrono lento bloqueia TUDO
function blockingCalc(n) {
  let sum = 0;
  for (let i = 0; i < n; i++) sum += i; // CPU-bound
  return sum;
}

// Bom: delegar para Worker Thread (CPU-bound) ou deixar para async (I/O)
// Regra: se uma operação leva >1ms de CPU, mova para Worker Thread

Memory Management e Profiling

O Node.js usa o motor V8 com garbage collector geracional. Entender o heap e as ferramentas de profiling é fundamental para diagnosticar vazamentos de memória em produção.

Estrutura do Heap V8

EspaçoDescrição
New Space (Young gen)Objetos recém-criados; GC frequente e rápido (Scavenge)
Old Space (Old gen)Objetos que sobreviveram ao GC jovem; GC lento (Mark-Sweep)
Code SpaceCódigo JIT compilado
Large Object SpaceObjetos >1 MB; não movidos pelo GC
// ── process.memoryUsage() — monitoramento em runtime ────────────
const mem = process.memoryUsage();
console.log({
  heapUsed:    `${(mem.heapUsed    / 1024 / 1024).toFixed(1)} MB`, // objetos JS vivos
  heapTotal:   `${(mem.heapTotal   / 1024 / 1024).toFixed(1)} MB`, // heap alocado pelo V8
  rss:         `${(mem.rss         / 1024 / 1024).toFixed(1)} MB`, // memória total do processo
  external:    `${(mem.external    / 1024 / 1024).toFixed(1)} MB`, // Buffers C++ (fora do heap JS)
  arrayBuffers:`${(mem.arrayBuffers/ 1024 / 1024).toFixed(1)} MB`,
});

// Alertar quando heap > 500 MB
setInterval(() => {
  const { heapUsed } = process.memoryUsage();
  if (heapUsed > 500 * 1024 * 1024) {
    console.error("ALERTA: heap > 500 MB, possível memory leak");
  }
}, 10_000);

// ── Aumentar limite do heap (flag de inicialização) ──────────────
// node --max-old-space-size=4096 app.js   → 4 GB de Old Space
// Útil para jobs batch que processam volumes grandes de dados

// ── Detectar memory leak — padrão de crescimento contínuo ────────
// Anti-pattern clássico: acumular em array global sem limpeza
const cache = new Map(); // OK se tiver eviction policy
const leak  = [];        // vazamento se crescer indefinidamente

// Prefira WeakMap/WeakRef para caches de objetos — GC pode coletar
const weakCache = new WeakMap(); // chaves são GC'd quando não há mais referências

// ── Heap snapshot via --inspect ──────────────────────────────────
// 1. Iniciar com: node --inspect app.js
//    (ou --inspect-brk para pausar no início)
// 2. Abrir Chrome: chrome://inspect → "Open dedicated DevTools for Node"
// 3. Aba "Memory" → "Take heap snapshot"
// 4. Tirar 2 snapshots (antes/depois de suspeita de leak)
// 5. Comparar com "Objects allocated between snapshots"
// Procure por: closures crescendo, arrays, EventEmitter listeners acumulando

// ── clinic.js — profiling avançado (instalar globalmente) ────────
// npm install -g clinic
//
// clinic doctor  -- node app.js   → diagnóstico geral (CPU, mem, I/O, event loop)
// clinic flame   -- node app.js   → flamegraph de CPU (onde o tempo é gasto)
// clinic bubbleprof -- node app.js → async profiling (onde o loop fica preso)
//
// Após gerar, abrir o HTML gerado no browser para análise visual

// ── Programatic heap snapshot (Node 11.13+) ──────────────────────
import v8 from "node:v8";
import fs from "node:fs";

function captureHeapSnapshot(filename = `heap-${Date.now()}.heapsnapshot`) {
  const snapshotStream = v8.writeHeapSnapshot(filename);
  console.log(`Heap snapshot salvo em: ${snapshotStream}`);
  return snapshotStream;
}

// Capturar em rota de diagnóstico (apenas em ambientes de dev/staging)
// GET /debug/heap-snapshot → captureHeapSnapshot()

| 22 LTS | 2024 | Active (até 2027) | require(esm) estável — importar ESM com require() sem flag; node:sqlite embutido; WebSocket nativo (new WebSocket()); import.meta.dirname/filename nativo; V8 12.4; Maglev compiler (startup mais rápido) |