Cheatsheet Node.js
Runtime JavaScript server-side
Node.js
Módulos e Setup
CommonJS (require)
// math.js
function somar(a, b) { return a + b; }
module.exports = { somar };
// app.js
const { somar } = require("./math");
console.log(somar(2, 3)); // 5O CommonJS é o sistema de módulos clássico do Node. Usa module.exports para exportar e require() para importar.
Módulos nativos
const fs = require("fs");
const path = require("path");
const http = require("http");
const os = require("os");
const crypto = require("crypto");
console.log(os.platform()); // "win32"
console.log(os.cpus().length); // 8O Node inclui módulos nativos (fs, path, http, os, crypto...) que não precisam de instalação. Importa-os diretamente pelo nome.
Variáveis de ambiente
// .env
PORT=3000
DB_URL=mongodb://localhost/app
// app.js
require("dotenv").config();
const porta = process.env.PORT || 3000;
// Node 20+ (sem dotenv):
// node --env-file=.env app.jsAs variáveis de ambiente guardam configurações fora do código. O dotenv carrega o ficheiro .env para process.env; no Node 20+ usa --env-file.
ES Modules (import)
// package.json
{ "type": "module" }
// math.js
export function somar(a, b) {
return a + b;
}
// app.js
import { somar } from "./math.js";Os ES Modules são o padrão moderno. Ativa com "type": "module" no package.json. A extensão .js é obrigatória no import.
Import dinâmico
// Carrega só quando necessário
const modulo = await import("./pesado.js");
// Condicional
if (process.env.DEBUG) {
const { log } = await import("./debug.js");
log("modo debug");
}O import() dinâmico carrega módulos sob demanda e retorna uma Promise. Útil para code-splitting e dependências opcionais.
Argumentos CLI
// node app.js --nome=Ana --idade=30
const args = process.argv.slice(2);
// Parse simples:
const params = Object.fromEntries(
args.map(a => a.replace("--", "").split("="))
);
console.log(params.nome); // "Ana"O process.argv contém os argumentos da linha de comandos. Os dois primeiros são o node e o ficheiro — usa slice(2) para os ignorar.
Exports: várias formas
// Várias funções:
module.exports = { somar, subtrair, PI };
// Uma classe:
module.exports = class Servidor { ... };
// Adicionar a exports existente:
exports.nome = "app";
exports.versao = "1.0";O module.exports é o objeto devolvido pelo require. O exports é um atalho — mas reatribuir exports = {} não funciona (usa module.exports).
__dirname e __filename
// CommonJS
console.log(__dirname); // pasta do ficheiro
console.log(__filename); // caminho completo
const path = require("path");
const config = path.join(__dirname, "config.json");No CommonJS, __dirname é a pasta do ficheiro atual e __filename o caminho completo. Essenciais para construir caminhos absolutos.
Eventos (EventEmitter)
const EventEmitter = require("events");
const emissor = new EventEmitter();
emissor.on("dados", (d) => {
console.log("Recebido:", d);
});
emissor.once("inicio", () => console.log("1x"));
emissor.emit("dados", { id: 1 });O EventEmitter é a base do padrão de eventos do Node. O on regista um listener, o emit dispara-o e o once corre só uma vez.
Default vs Nomeado
// math.js (ESM)
export default function somar(a, b) {
return a + b;
}
export const PI = 3.14;
// app.js
import somar, { PI } from "./math.js";Um módulo pode ter um export default (importado sem chavetas) e vários exports nomeados (com chavetas). Combina os dois no mesmo import.
import.meta (ESM)
// ES Modules (não tem __dirname)
import { fileURLToPath } from "url";
import path from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);Nos ES Modules não existe __dirname. Usa import.meta.url com fileURLToPath para obter o caminho do ficheiro.
Sistema de Ficheiros
Ler ficheiro
const fs = require("fs/promises");
// Assíncrono (recomendado)
const conteudo = await fs.readFile(
"dados.txt", "utf-8"
);
// Síncrono (bloqueia o event loop)
const fsSync = require("fs");
const txt = fsSync.readFileSync("dados.txt", "utf-8");Usa fs/promises com await para não bloquear. O readFileSync só é aceitável em scripts de arranque ou CLI.
Listar e verificar
const fs = require("fs/promises");
// Listar diretório
const itens = await fs.readdir("./src");
// Com tipo de ficheiro:
const comTipo = await fs.readdir("./src", {
withFileTypes: true,
});
comTipo.filter(e => e.isFile());
// Verificar existência
await fs.access("ficheiro.txt"); // lança erro se não existirO readdir lista o conteúdo de uma pasta. Com withFileTypes obténs o tipo de cada entrada. O access verifica se existe (lança erro caso contrário).
Apagar e renomear
const fs = require("fs/promises");
// Apagar ficheiro
await fs.unlink("temp.txt");
// Apagar pasta (com conteúdo)
await fs.rm("pasta", { recursive: true });
// Renomear / mover
await fs.rename("antigo.txt", "novo.txt");O unlink apaga ficheiros. O rm com recursive apaga pastas inteiras. O rename também serve para mover.
Escrever ficheiro
const fs = require("fs/promises");
// Sobrescrever (cria se não existir)
await fs.writeFile("log.txt", "Olá");
// Acrescentar no fim
await fs.appendFile("log.txt", "\nNova linha");
// Criar pasta (com subpastas)
await fs.mkdir("pasta/sub", { recursive: true });O writeFile sobrescreve o conteúdo e o appendFile adiciona ao fim. O mkdir com recursive cria toda a hierarquia.
Módulo path
const path = require("path");
path.join(__dirname, "src", "app.js")
path.basename("/a/b/f.txt") // "f.txt"
path.extname("foto.png") // ".png"
path.resolve("./config") // caminho absoluto
path.dirname("/a/b/f.txt") // "/a/b"O path manipula caminhos de forma segura em qualquer SO. O join concatena com o separador correto (/ ou \).
Informações (stat)
const fs = require("fs/promises");
const info = await fs.stat("ficheiro.txt");
info.size // tamanho em bytes
info.isFile() // true
info.isDirectory() // false
info.mtime // última modificação
info.birthtime // criaçãoO stat devolve metadados do ficheiro: tamanho, tipo e datas. Útil para verificar antes de processar ou para mostrar ao utilizador.
Copiar ficheiros
const fs = require("fs/promises");
// Ficheiro único
await fs.copyFile("origem.txt", "destino.txt");
// Pasta inteira (Node 16.7+)
await fs.cp("pasta-src", "pasta-dst", {
recursive: true,
});O copyFile copia um ficheiro. O cp com recursive copia pastas inteiras — útil para backups e templates.
Streams (ficheiros grandes)
const fs = require("fs");
const leitura = fs.createReadStream("grande.csv");
const escrita = fs.createWriteStream("copia.csv");
leitura.pipe(escrita);
leitura.on("data", (chunk) => {
console.log("Lido:", chunk.length, "bytes");
});
leitura.on("end", () => console.log("Fim"));Os streams processam ficheiros em blocos (chunks) sem carregar tudo na memória. O pipe liga a leitura à escrita automaticamente.
Observar alterações (watch)
const fs = require("fs");
const watcher = fs.watch("./src", (evento, ficheiro) => {
console.log(evento, ficheiro); // "change" "app.js"
});
// Parar de observar
watcher.close();O fs.watch notifica quando ficheiros mudam (criar, alterar, apagar). Útil para build tools e live reload. Chama close() para parar.
Ler e escrever JSON
const fs = require("fs/promises");
// Ler
const texto = await fs.readFile("config.json", "utf-8");
const config = JSON.parse(texto);
// Escrever (formatado)
await fs.writeFile(
"config.json",
JSON.stringify(config, null, 2)
);Combina readFile/writeFile com JSON.parse e JSON.stringify. O terceiro argumento (2) formata com indentação.
Stream pipeline
const { pipeline } = require("stream/promises");
const fs = require("fs");
const zlib = require("zlib");
await pipeline(
fs.createReadStream("dados.csv"),
zlib.createGzip(),
fs.createWriteStream("dados.csv.gz")
);O pipeline encadeia streams e propaga erros corretamente (ao contrário do pipe simples). Ideal para transformar e comprimir dados.
Assincronismo
Promises
function esperar(ms) {
return new Promise((resolve) =>
setTimeout(resolve, ms)
);
}
esperar(1000).then(() => {
console.log("1s depois");
});
// Rejeitar:
new Promise((_, reject) =>
reject(new Error("falhou"))
);Uma Promise representa um valor futuro. O resolve indica sucesso e o reject indica erro. Consome-se com .then().
setTimeout / setInterval
// Uma vez após 2s
setTimeout(() => {
console.log("atrasado");
}, 2000);
// Repetir a cada 1s
const id = setInterval(() => {
console.log("tick");
}, 1000);
clearInterval(id); // parar
clearTimeout(id); // cancelar timeoutO setTimeout executa uma vez após um atraso e o setInterval repete periodicamente. Guarda o ID para cancelar com clear.
AbortController
const controller = new AbortController();
// Cancelar após 3s
const timeout = setTimeout(
() => controller.abort(), 3000
);
try {
const resp = await fetch(url, {
signal: controller.signal,
});
} catch (e) {
console.log("Cancelado:", e.name); // "AbortError"
}O AbortController cancela operações assíncronas (como fetch). Passa o signal e chama abort() para interromper.
async / await
async function buscarDados() {
try {
const resp = await fetch(url);
const dados = await resp.json();
return dados;
} catch (erro) {
console.error("Falha:", erro.message);
throw erro;
}
}O async/await torna o código assíncrono legível como síncrono. O await pausa até a Promise resolver. O try/catch trata erros.
setImmediate / nextTick
console.log("1 - síncrono");
process.nextTick(() => console.log("2 - nextTick"));
Promise.resolve().then(() => console.log("3 - microtask"));
setImmediate(() => console.log("4 - immediate"));
setTimeout(() => console.log("5 - timer"), 0);
// Ordem típica: 1, 2, 3, 4/5O process.nextTick corre antes das microtasks. O setImmediate executa na fase de check do event loop. Ambos adiam código sem usar timers.
Concorrência limitada
async function mapLimit(itens, limite, fn) {
const resultados = [];
for (let i = 0; i < itens.length; i += limite) {
const lote = itens.slice(i, i + limite);
resultados.push(...await Promise.all(lote.map(fn)));
}
return resultados;
}
await mapLimit(urls, 5, (u) => fetch(u));Para não abrir centenas de pedidos em paralelo, processa em lotes (Promise.all por grupo). Controla o consumo de memória e sockets.
Promise.all / allSettled
// Todas em paralelo (falha se uma falhar)
const [a, b, c] = await Promise.all([
fetch("/api/a"),
fetch("/api/b"),
fetch("/api/c"),
]);
// Todas (mesmo com erros)
const resultados = await Promise.allSettled(promessas);
resultados.filter(r => r.status === "fulfilled");O Promise.all corre em paralelo mas rejeita se uma falhar. O allSettled espera todas e devolve o estado de cada uma.
Promisify (callbacks)
const { promisify } = require("util");
const fs = require("fs");
// Converter callback → Promise
const ler = promisify(fs.readFile);
const conteudo = await ler("dados.txt", "utf-8");
// Hoje prefere-se fs/promises:
const fsp = require("fs/promises");O util.promisify converte funções de callback (estilo (err, resultado)) em Promises. Hoje, prefere os módulos */promises nativos.
Tratar erros (padrão)
// Wrapper para evitar try/catch repetido
async function capturar(promise) {
try {
const dados = await promise;
return [dados, null];
} catch (erro) {
return [null, erro];
}
}
const [dados, erro] = await capturar(fetch(url));
if (erro) console.error(erro);Este padrão [dados, erro] (inspirado no Go) evita try/catch aninhado. Verifica se há erro antes de usar os dados.
Promise.race / any
// Primeira a resolver OU rejeitar
const maisRapida = await Promise.race([
fetch("/api/lento"),
esperar(5000).then(() => "timeout"),
]);
// Primeira a resolver (ignora erros)
const primeira = await Promise.any([
fetch("/api/servidor1"),
fetch("/api/servidor2"),
]);O race devolve o primeiro resultado (sucesso ou erro). O any devolve o primeiro sucesso, ignorando rejeições. Útil para timeouts.
Async iterators (for await)
const fs = require("fs");
// Ler stream linha a linha
const stream = fs.createReadStream("grande.txt");
for await (const chunk of stream) {
console.log("Bloco:", chunk.length);
}O for await...of percorre fontes assíncronas (streams, async generators) de forma sequencial. Consome cada bloco à medida que chega.
Event Loop (conceito)
console.log("1 - síncrono");
setTimeout(() => console.log("3 - timer"), 0);
Promise.resolve().then(
() => console.log("2 - microtask")
);
// Ordem: 1, 2, 3
// Síncrono → Microtasks → TimersO Event Loop processa: código síncrono → microtasks (Promises) → timers (setTimeout). Mesmo com delay 0, o timer corre por último.
HTTP e Servidor
Servidor HTTP nativo
const http = require("http");
const servidor = http.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/plain",
});
res.end("Olá do Node!");
});
servidor.listen(3000, () => {
console.log("Em http://localhost:3000");
});O módulo http cria um servidor sem dependências. O callback recebe o pedido (req) e a resposta (res). O listen inicia na porta.
Servir ficheiros estáticos
const http = require("http");
const fs = require("fs");
const path = require("path");
http.createServer((req, res) => {
const ficheiro = path.join("./public", req.url);
fs.readFile(ficheiro, (erro, dados) => {
if (erro) {
res.writeHead(404);
return res.end("Não encontrado");
}
res.end(dados);
});
}).listen(3000);Um servidor de ficheiros estáticos lê do disco e devolve ao cliente. Em produção, usa o Express (express.static) ou um CDN.
Redirecionamentos
// 301 (permanente)
res.writeHead(301, { Location: "/nova-pagina" });
res.end();
// 302 (temporário)
res.writeHead(302, { Location: "/login" });
res.end();O 301 é permanente (o SEO transfere para o novo URL). O 302 é temporário. O browser segue o header Location automaticamente.
fetch (Node 18+)
// GET
const resp = await fetch("https://api.exemplo.com/dados");
const dados = await resp.json();
// POST
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome: "Ana" }),
});Desde o Node 18, o fetch é nativo (sem instalar nada). Funciona como no browser: resp.json() para ler o corpo.
URL e query params
const url = new URL(
"http://site.com/busca?q=node&p=2"
);
url.pathname // "/busca"
url.searchParams.get("q") // "node"
url.searchParams.get("p") // "2"
// Construir URL:
const u = new URL("http://api.com/dados");
u.searchParams.set("page", "1");A classe URL analisa e constrói URLs. O searchParams dá acesso fácil aos parâmetros de query string.
HTTPS / TLS
const https = require("https");
const fs = require("fs");
const opcoes = {
key: fs.readFileSync("chave.pem"),
cert: fs.readFileSync("cert.pem"),
};
https.createServer(opcoes, (req, res) => {
res.end("ligação segura");
}).listen(443);O módulo https cria um servidor encriptado. Precisa de uma chave (key) e certificado (cert). Em produção, usa-se normalmente um reverse proxy.
fetch com timeout
const resp = await fetch(url, {
signal: AbortSignal.timeout(5000), // 5s
});
if (!resp.ok) {
throw new Error("HTTP " + resp.status);
}
const dados = await resp.json();O AbortSignal.timeout() cancela o fetch após um limite. Verifica sempre resp.ok — o fetch não lança erro em status 4xx/5xx.
Headers e status
// Ler headers do pedido:
req.headers["content-type"]
req.headers["authorization"]
// Definir na resposta:
res.writeHead(200, {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
"X-Custom": "valor",
});Os headers transportam metadados (tipo de conteúdo, autenticação, cache). Lê-os em req.headers e define-os com writeHead.
Ler corpo do pedido (POST)
http.createServer((req, res) => {
let corpo = "";
req.on("data", (chunk) => { corpo += chunk; });
req.on("end", () => {
const dados = JSON.parse(corpo);
res.end("Recebido: " + dados.nome);
});
}).listen(3000);No servidor nativo, o corpo chega em blocos via eventos data. Acumula-os e processa no evento end. O Express simplifica isto com express.json().
Cookies
// Definir cookie na resposta:
res.writeHead(200, {
"Set-Cookie": "sessao=abc123; HttpOnly; Path=/",
});
// Ler cookies do pedido:
const cookies = req.headers.cookie; // "sessao=abc123"Os cookies são definidos com o header Set-Cookie e lidos em req.headers.cookie. O flag HttpOnly impede acesso via JavaScript.
Express.js
Setup Express
npm install express
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.listen(3000, () => {
console.log("Servidor ativo na porta 3000");
});O Express é o framework web mais popular do Node. O express.json() faz parse do corpo JSON automaticamente.
Middleware
// Logger personalizado
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // passar ao seguinte
});
// Middleware de erro (4 argumentos)
app.use((erro, req, res, next) => {
console.error(erro.stack);
res.status(500).json({ erro: erro.message });
});O middleware corre entre o pedido e a rota. O next() passa ao seguinte. O handler de erro tem 4 argumentos e deve ser o último.
Validação com middleware
function validarId(req, res, next) {
const id = Number(req.params.id);
if (isNaN(id)) {
return res.status(400).json({
erro: "ID deve ser numérico",
});
}
req.id = id;
next();
}
app.get("/users/:id", validarId, (req, res) => {
res.json({ id: req.id });
});Um middleware de validação verifica os dados antes da rota. Se falhar, responde com erro e não chama next().
Rotas e parâmetros
app.get("/", (req, res) => {
res.send("Início");
});
// Parâmetro de rota
app.get("/users/:id", (req, res) => {
res.json({ id: req.params.id });
});
// Query string ?busca=x
app.get("/busca", (req, res) => {
res.json({ q: req.query.busca });
});As rotas mapeiam método + caminho. Os :params capturam segmentos da URL. O req.query lê a query string.
Encadear handlers
function validar(req, res, next) {
if (!req.body.nome) return next(new Error("nome"));
next();
}
function autenticar(req, res, next) {
if (!req.user) return res.status(401).end();
next();
}
app.post("/dados", autenticar, validar, (req, res) => {
res.json({ ok: true });
});Podes passar vários handlers numa rota — correm em sequência. Cada um chama next() para avançar ou responde/lança erro para parar.
Upload de ficheiros (multer)
npm install multer
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
app.post("/upload", upload.single("ficheiro"),
(req, res) => {
console.log(req.file.originalname);
console.log(req.file.path);
res.json({ ok: true });
}
);O multer processa uploads multipart. O single() aceita um ficheiro. Os dados ficam em req.file (nome, caminho, tamanho).
Objeto req (pedido)
app.post("/users/:id", (req, res) => {
req.params.id // parâmetro da rota
req.query.ativo // query string
req.body // corpo (com express.json)
req.headers // cabeçalhos
req.method // "POST"
req.path // "/users/5"
req.ip // IP do cliente
});O objeto req traz tudo sobre o pedido: params, query, body, headers, method e ip.
Respostas (res)
res.send("texto HTML");
res.json({ ok: true });
res.status(201).json({ id: 1 });
res.redirect("/login");
res.download("/ficheiro.pdf");
res.sendFile(path.join(__dirname, "index.html"));
res.status(204).end(); // sem corpoO objeto res tem métodos para cada tipo de resposta. O json() define o Content-Type automaticamente. O 204 é "sucesso sem conteúdo".
app.set e configuração
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");
// Ler configuração
const porta = app.get("port");
// Ativar/desativar opções
app.enable("trust proxy");
app.disable("x-powered-by");
app.listen(app.get("port"));O app.set guarda pares chave-valor de configuração e o app.get("chave") lê-os. O enable/disable ativam flags booleanas.
Router modular
// routes/produtos.js
const router = express.Router();
router.get("/", listar);
router.post("/", criar);
router.get("/:id", ver);
router.put("/:id", atualizar);
router.delete("/:id", eliminar);
module.exports = router;
// app.js
app.use("/produtos", require("./routes/produtos"));O Router organiza as rotas em ficheiros separados. Cada módulo trata um recurso. O prefixo é definido no app.use.
Ficheiros estáticos
// Servir pasta public/
app.use(express.static("public"));
// Com prefixo
app.use("/assets", express.static("public/assets"));
// Aceder: http://localhost:3000/img/logo.png
// Ou: http://localhost:3000/assets/style.cssO express.static serve ficheiros diretamente (CSS, JS, imagens). Sem prefixo, o ficheiro é acedido pela raiz; com prefixo, fica sob esse caminho.
404 e handler de erro
// 404: nenhuma rota correspondeu (no fim)
app.use((req, res) => {
res.status(404).json({ erro: "Não encontrado" });
});
// Handler de erro (4 args, depois do 404)
app.use((erro, req, res, next) => {
res.status(erro.status || 500).json({
erro: erro.message,
});
});Um middleware final sem caminho captura rotas inexistentes (404). O handler de erro (4 args) centraliza falhas. Ambos devem ficar no fim.
API REST e Auth
CRUD completo
let produtos = [];
app.get("/produtos", (req, res) =>
res.json(produtos));
app.post("/produtos", (req, res) => {
const novo = { id: Date.now(), ...req.body };
produtos.push(novo);
res.status(201).json(novo);
});
app.delete("/produtos/:id", (req, res) => {
produtos = produtos.filter(p => p.id != req.params.id);
res.status(204).end();
});Um CRUD usa GET (listar), POST (criar, 201), PUT (atualizar) e DELETE (204). Os dados vêm em req.body.
Tratamento de erros
class ApiError extends Error {
constructor(status, mensagem) {
super(mensagem);
this.status = status;
}
}
// Na rota:
throw new ApiError(404, "Produto não encontrado");
// Handler global (último middleware):
app.use((erro, req, res, next) => {
const status = erro.status || 500;
res.status(status).json({ erro: erro.message });
});Cria uma classe ApiError com o status HTTP. O handler global centraliza todas as respostas de erro num só lugar.
Hash de passwords (bcrypt)
npm install bcrypt
const bcrypt = require("bcrypt");
// Registo: guardar hash (nunca a senha em texto)
const hash = await bcrypt.hash(senha, 10);
// Login: comparar
const ok = await bcrypt.compare(senha, hash);
if (!ok) return res.status(401).end();Nunca guardes passwords em texto. O bcrypt.hash gera um hash com salt (custo 10) e o compare verifica no login.
Códigos de estado HTTP
res.status(200) // OK res.status(201) // Criado res.status(204) // Sucesso, sem conteúdo res.status(400) // Pedido inválido res.status(401) // Não autenticado res.status(403) // Sem permissão res.status(404) // Não encontrado res.status(500) // Erro do servidor
Os códigos comunicam o resultado: 2xx sucesso, 4xx erro do cliente, 5xx erro do servidor. Usa-os corretamente em vez de devolver sempre 200.
CORS
npm install cors
const cors = require("cors");
// Liberar tudo (desenvolvimento)
app.use(cors());
// Configuração específica (produção)
app.use(cors({
origin: "http://localhost:5173",
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
}));O CORS controla quais domínios podem aceder à API. Em produção, especifica a origin exata em vez de liberar tudo.
Paginação
app.get("/produtos", (req, res) => {
const pagina = parseInt(req.query.page) || 1;
const limite = parseInt(req.query.limit) || 10;
const inicio = (pagina - 1) * limite;
const resultados = produtos.slice(inicio, inicio + limite);
res.json({
dados: resultados,
total: produtos.length,
pagina,
paginas: Math.ceil(produtos.length / limite),
});
});A paginação devolve só uma fatia dos dados. O cliente envia page e limit. A resposta inclui o total para o frontend.
Validação de input
function validarProduto(req, res, next) {
const { nome, preco } = req.body;
if (!nome || nome.length < 3) {
return res.status(400).json({
erro: "Nome deve ter 3+ caracteres",
});
}
if (typeof preco !== "number" || preco < 0) {
return res.status(400).json({ erro: "Preço inválido" });
}
next();
}
app.post("/produtos", validarProduto, criar);Valida sempre os dados do cliente. Verifica tipo, tamanho e formato. Responde com 400 e uma mensagem clara se algo falhar.
Rate limiting
npm install express-rate-limit
const limitador = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // 100 pedidos por IP
message: { erro: "Muitos pedidos" },
});
app.use("/api/", limitador);O rate limiting protege contra abuso e DDoS. Limita o número de pedidos por IP numa janela de tempo (windowMs).
Versionamento de API
// Por prefixo na URL (mais comum)
app.use("/api/v1/produtos", routerV1);
app.use("/api/v2/produtos", routerV2);
// Ou por Router
const v1 = express.Router();
v1.get("/produtos", listarV1);
app.use("/api/v1", v1);Versionar a API (ex.: /api/v1) permite evoluir sem quebrar clientes antigos. Cada versão tem o seu próprio router.
Validar com Zod
npm install zod
const { z } = require("zod");
const schema = z.object({
nome: z.string().min(3),
preco: z.number().positive(),
});
app.post("/produtos", (req, res) => {
const resultado = schema.safeParse(req.body);
if (!resultado.success) {
return res.status(400).json(resultado.error.issues);
}
// resultado.data é validado e tipado
});O Zod valida e transforma dados com esquemas declarativos. O safeParse não lança erro — devolve success e os issues.
Autenticação JWT
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
// Gerar token:
const token = jwt.sign(
{ id: user.id }, "segredo", { expiresIn: "1h" }
);
// Verificar (middleware):
function auth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
try {
req.user = jwt.verify(token, "segredo");
next();
} catch {
res.status(401).json({ erro: "Não autorizado" });
}
}O JWT autentica sem sessão. O token vai no header Authorization: Bearer .... O middleware verifica e injeta o utilizador em req.user.
Logs de requests (morgan)
npm install morgan
const morgan = require("morgan");
// Formato pré-definido
app.use(morgan("dev")); // colorido para dev
app.use(morgan("combined")); // formato Apache
// Saída personalizada
app.use(morgan(":method :url :status :response-time ms"));O morgan regista cada pedido HTTP (método, URL, status, tempo). O formato dev é colorido; combined serve para produção.
NPM e Pacotes
Iniciar projeto
npm init -y // criar package.json npm install // instalar dependências npm install express // dependência de produção npm install -D nodemon // dependência de dev npm install -g pm2 // global (CLI)
O npm init -y cria o package.json com valores por defeito. O -D guarda em devDependencies (só para desenvolvimento).
package-lock.json
// O lock regista versões EXATAS de tudo
// (incluindo dependências transitivas)
npm ci // instalação limpa baseada no lock
// (mais rápido, reprodutível)
// Regras:
// - Nunca editar manualmente
// - Sempre commitar ao git
// - Usar npm ci em CI/CDO package-lock.json garante que todos instalam exatamente as mesmas versões. O npm ci é ideal para servidores e CI.
engines e tipo de módulo
// package.json
{
"type": "module", // usar ES Modules
"engines": {
"node": ">=20.0.0" // versão mínima
}
}
// "type": "commonjs" é o padrão (require)O "type": "module" ativa os ES Modules em ficheiros .js. O engines documenta a versão mínima do Node necessária.
Remover e listar pacotes
npm uninstall pacote // remover npm uninstall -D nodemon // remover de dev npm ls // árvore de dependências npm ls --depth=0 // só diretas npm outdated // versões desatualizadas npm audit // vulnerabilidades
O uninstall remove um pacote e atualiza o package.json. O npm ls mostra a árvore e o audit deteta vulnerabilidades conhecidas.
npx
// Executar sem instalar globalmente npx create-react-app minha-app npx nodemon app.js npx jest --watch // Versão específica npx node@18 -v // Pacotes locais do node_modules/.bin npx eslint src/
O npx executa pacotes sem os instalar globalmente. Se o pacote existe em node_modules, usa-o; senão, descarrega temporariamente.
.npmrc e registries
# .npmrc (projeto ou ~/.npmrc)
registry=https://registry.npmjs.org/
@minha-empresa:registry=https://npm.empresa.com/
//npm.empresa.com/:_authToken=${NPM_TOKEN}
# Guardar token com login:
npm login --registry=https://npm.empresa.com/O .npmrc configura o registry e tokens de autenticação. Permite usar registries privados para pacotes com scope (@empresa).
Scripts do package.json
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"lint": "eslint src/"
}
}
// Executar:
npm run dev
npm start // "start" não precisa de "run"
npm testOs scripts automatizam comandos. npm run nome executa qualquer script. O start e test funcionam sem run.
Workspaces (monorepo)
// package.json (raiz)
{
"workspaces": ["packages/*"]
}
// Estrutura:
// packages/api/package.json
// packages/frontend/package.json
// packages/shared/package.json
npm install // instala tudo
npm run dev -w api // corre num workspaceOs workspaces gerem múltiplos pacotes num só repositório. As dependências partilhadas são instaladas uma vez na raiz.
Publicar pacote
// package.json mínimo:
{
"name": "meu-pacote",
"version": "1.0.0",
"main": "index.js"
}
npm login
npm publish
// Atualizar versão:
npm version patch // 1.0.0 → 1.0.1
npm version minor // 1.0.1 → 1.1.0
npm version major // 1.1.0 → 2.0.0Para publicar, precisas de conta no npmjs.com. O npm version atualiza a versão e cria um commit/tag automaticamente.
Gestão de versões (semver)
npm install pacote // última versão npm install pacote@1.2.3 // versão exata npm install pacote@^1.2.0 // compatível 1.x.x npm install pacote@~1.2.0 // só patches 1.2.x npm outdated // ver desatualizados npm update // atualizar dentro do range
No semver: ^ aceita minor+patch, ~ só patch. O outdated mostra o que está velho e o update respeita os ranges.
bin e CLI de pacote
// package.json
{
"name": "minha-cli",
"bin": { "minha-cli": "./cli.js" }
}
// cli.js (primeira linha)
#!/usr/bin/env node
console.log("Olá da CLI!");
// npm link → torna o comando disponívelO campo bin regista comandos executáveis do pacote. O shebang #!/usr/bin/env node indica o interpretador. O npm link instala localmente para teste.
CLI, Testes e Boas Práticas
Modo watch (Node 18+)
// Reiniciar ao guardar ficheiro node --watch app.js // Com variáveis de ambiente node --watch --env-file=.env app.js // Ver versões node -v npm -v
O --watch reinicia o servidor automaticamente quando guardas o ficheiro. Substitui o nodemon em projetos simples.
Estrutura recomendada
projeto/
src/
routes/ // rotas por recurso
controllers/ // lógica dos handlers
services/ // regra de negócio
models/ // acesso a dados
middleware/ // validação, auth
app.js // configuração Express
package.json
.env
.gitignoreSepara por responsabilidade: routes definem URLs, controllers orquestram, services contêm a lógica. Cada ficheiro faz uma coisa.
Testes de API (supertest)
npm install -D supertest vitest
const request = require("supertest");
it("cria produto", async () => {
const resp = await request(app)
.post("/produtos")
.send({ nome: "Teclado", preco: 50 });
expect(resp.status).toBe(201);
expect(resp.body.id).toBeDefined();
});O supertest testa endpoints HTTP sem levantar o servidor. Passa o app do Express e simula pedidos com get/post e send.
Debugging
// Inspecionar com Chrome DevTools
node --inspect app.js
node --inspect-brk app.js // pausa no início
// Logs úteis
console.log("info");
console.warn("aviso");
console.error("erro");
console.table(arrayDeObjetos);
console.time("op");
console.timeEnd("op"); // "op: 123ms"O --inspect abre o Chrome DevTools para depurar. O console.table mostra arrays/objetos em tabela. O time/timeEnd mede duração.
Boas práticas
// Usar async/await (não callbacks) // Sempre tratar erros (try/catch) // Config em variáveis de ambiente // Módulos pequenos e focados // ESLint + Prettier para consistência // Nunca commitar node_modules // Usar .gitignore e .env.example
Convenções que mantêm o código Node limpo e pronto para produção. O .env.example documenta as variáveis necessárias sem expor segredos.
Segurança básica (helmet)
npm install helmet
const helmet = require("helmet");
app.use(helmet()); // headers de segurança
// Nunca expor stack traces em produção:
app.use((erro, req, res, next) => {
res.status(500).json({
erro: process.env.NODE_ENV === "production"
? "Erro interno"
: erro.message,
});
});O helmet adiciona headers de segurança automaticamente. Em produção, nunca exponhas stack traces — mostra só "Erro interno".
Process e encerramento
// Encerrar graciosamente (Ctrl+C)
process.on("SIGINT", () => {
console.log("A encerrar...");
servidor.close(() => process.exit(0));
});
// Erro não capturado
process.on("uncaughtException", (e) => {
console.error("Fatal:", e);
process.exit(1);
});
// Promise rejeitada sem catch
process.on("unhandledRejection", (r) => {
console.error("Rejeitada:", r);
});Trata o SIGINT para encerrar sem perder dados. O uncaughtException é o último recurso — regista o erro e sai.
ESLint e Prettier
npm install -D eslint prettier // eslint.config.js (flat config) npx eslint --init // Formatar ao guardar (VS Code) // "editor.formatOnSave": true // "editor.defaultFormatter": "esbenp.prettier-vscode" npx eslint src/ --fix
O ESLint deteta erros e problemas de estilo; o Prettier formata o código automaticamente. Usa-os juntos para consistência.
Performance e profiling
// Gerar perfil de CPU
node --prof app.js
node --prof-process isolate-*.log > perfil.txt
// Heap snapshot (memória)
node --inspect app.js
// → DevTools → Memory → Take snapshot
// Monitorizar event loop
const { monitorEventLoopDelay } = require("perf_hooks");O --prof gera perfis de CPU e o --inspect permite heap snapshots no DevTools. Essenciais para encontrar bottlenecks e fugas de memória.
NODE_ENV e configuração
const env = process.env.NODE_ENV || "development";
const config = {
development: { debug: true, dbUrl: "localhost" },
production: { debug: false, dbUrl: process.env.DB_URL },
}[env];
// Arrancar:
// NODE_ENV=production node app.jsO NODE_ENV distingue ambientes (development/production). Ajusta comportamento como debug, logging e ligações à base de dados.
Testes (Vitest / Jest)
npm install -D vitest
// math.test.js
import { describe, it, expect } from "vitest";
import { somar } from "./math.js";
describe("somar", () => {
it("soma dois números", () => {
expect(somar(2, 3)).toBe(5);
});
});
// npx vitestO Vitest (ou Jest) testa funções isoladamente. O describe agrupa testes e o expect verifica resultados. Corre com npx vitest.
Avançado
Buffers (dados binários)
const buf = Buffer.from("Olá", "utf-8");
buf.length // 4 bytes
buf.toString("utf-8") // "Olá"
buf.toString("base64") // "T2zDoA=="
// Alocar e escrever
const b = Buffer.alloc(8);
b.writeUInt32BE(1234, 0);
Buffer.concat([buf, b]);O Buffer representa dados binários brutos (fora da string). Essencial para ficheiros, rede e encriptação. Converte com toString(encoding).
Crypto: Hash
const crypto = require("crypto");
const hash = crypto
.createHash("sha256")
.update("texto secreto")
.digest("hex");
// MD5 (não usar para segurança)
crypto.createHash("md5").update("x").digest("hex");O crypto.createHash gera hashes unidirecionais (sha256, md5). Usa SHA-256 para integridade; para passwords prefere bcrypt.
Socket.IO
npm install socket.io
const io = require("socket.io")(servidorHttp);
io.on("connection", (socket) => {
socket.on("chat", (msg) => {
io.emit("chat", msg); // enviar a todos
});
socket.join("sala1");
io.to("sala1").emit("nova", "dados");
});O Socket.IO acrescenta salas, eventos e fallback automático sobre WebSockets. O emit envia a todos; to(sala) restringe a um grupo.
Child Processes
const { exec, spawn } = require("child_process");
// exec: saída em buffer (comandos curtos)
exec("ls -la", (erro, stdout) => {
console.log(stdout);
});
// spawn: stream (saída longa / tempo real)
const proc = spawn("node", ["-v"]);
proc.stdout.on("data", (d) => console.log(d.toString()));O módulo child_process executa comandos externos. O exec devolve a saída completa; o spawn transmite em stream (melhor para processos longos).
Crypto: HMAC e tokens
const crypto = require("crypto");
// HMAC (hash com chave secreta)
const hmac = crypto
.createHmac("sha256", "segredo")
.update("dados")
.digest("hex");
// Token aleatório seguro
const token = crypto.randomBytes(32).toString("hex");O createHmac gera um hash autenticado com uma chave (verifica integridade + origem). O randomBytes cria tokens criptograficamente seguros.
Módulo os (sistema)
const os = require("os");
os.platform() // "win32" | "linux" | "darwin"
os.arch() // "x64" | "arm64"
os.cpus().length // núcleos de CPU
os.totalmem() // RAM total (bytes)
os.freemem() // RAM livre
os.homedir() // pasta do utilizador
os.tmpdir() // pasta temporáriaO módulo os dá informações do sistema operativo: plataforma, arquitetura, CPU e memória. Útil para adaptar o comportamento ao ambiente.
Cluster (multi-core)
const cluster = require("cluster");
const os = require("os");
if (cluster.isPrimary) {
const numCpus = os.cpus().length;
for (let i = 0; i < numCpus; i++) {
cluster.fork(); // um worker por CPU
}
} else {
require("./servidor"); // cada worker corre o servidor
}O cluster cria um processo por núcleo de CPU, partilhando a mesma porta. Contorna o limite single-thread do Node para usar todos os cores.
Crypto: encriptação AES
const crypto = require("crypto");
const chave = crypto.scryptSync("senha", "salt", 32);
const iv = crypto.randomBytes(16);
const cifra = crypto.createCipheriv("aes-256-cbc", chave, iv);
const encriptado = Buffer.concat([
cifra.update("mensagem secreta", "utf-8"),
cifra.final(),
]);O createCipheriv encripta com AES (reversível). Deriva a chave com scryptSync e usa um iv aleatório. Para desencriptar usa createDecipheriv.
Performance (perf_hooks)
const { performance } = require("perf_hooks");
const inicio = performance.now();
// ... operação ...
const duracao = performance.now() - inicio;
console.log(`Demorou ${duracao.toFixed(2)}ms`);
// Medir com marcador
performance.mark("inicio");
performance.mark("fim");
performance.measure("op", "inicio", "fim");O perf_hooks mede tempo com alta precisão (milissegundos decimais). O performance.now() é mais preciso que Date.now().
Worker Threads
const { Worker, isMainThread, parentPort } =
require("worker_threads");
if (isMainThread) {
const w = new Worker(__filename);
w.postMessage("calcular");
w.on("message", (r) => console.log(r));
} else {
parentPort.on("message", () => {
parentPort.postMessage("feito: " + 40 * 40);
});
}Os worker_threads correm JavaScript em paralelo (threads reais), ideal para tarefas CPU-intensive. Comunicam por postMessage.
WebSockets (ws)
npm install ws
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
ws.on("message", (msg) => {
ws.send("eco: " + msg);
});
});Os WebSockets mantêm uma ligação bidirecional em tempo real. O pacote ws cria o servidor; cada connection recebe e envia mensagens.