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/CDTesting: 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ão | Ano | Status | Principais novidades |
|---|---|---|---|
| 16 LTS | 2021 | EOL (2023) | V8 9.x; npm 8 com workspaces; Apple Silicon nativo; Promise-based APIs estáveis (fs/promises); --experimental-fetch |
| 18 LTS | 2022 | Maintenance (até 2025) | fetch nativo estável; Web Streams API; node:test runner embutido (experimental); V8 10.2; --experimental-vm-modules |
| 20 LTS | 2023 | Active (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ário | Use |
|---|---|
| CPU-bound: compressão, hash, parsing | Worker Threads |
| I/O-bound: banco, rede, arquivos | Event loop + async (sem workers) |
| Isolamento total, crash containment | child_process / cluster |
| Código legado sem thread safety | child_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ção | Shell? | Streaming? | Uso típico |
|---|---|---|---|
exec() | Sim | Não (buffer) | Comandos curtos, saída pequena |
execFile() | Não | Não (buffer) | Binário sem shell overhead |
spawn() | Não | Sim | Processos longos, saída grande |
fork() | Não | Via IPC | Subprocesso 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.nextTick → Promise.then/catch/finally → queueMicrotask
// ── 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 ThreadMemory 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ço | Descriçã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 Space | Código JIT compilado |
| Large Object Space | Objetos >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) |