DevTools

Cheatsheet Node.js

Runtime JavaScript server-side

Voltar às linguagens
Node.js
101 cards encontrados
Categorias:
Versões:

Módulos e Setup


11 cards
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)); // 5

O 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); // 8

O 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.js

As 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


11 cards
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 existir

O 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ção

O 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


12 cards
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 timeout

O 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/5

O 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 → Timers

O Event Loop processa: código síncrono → microtasks (Promises) → timers (setTimeout). Mesmo com delay 0, o timer corre por último.

HTTP e Servidor


10 cards
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


12 cards
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 corpo

O 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.css

O 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


12 cards
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


11 cards
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/CD

O 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 test

Os 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 workspace

Os 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.0

Para 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ível

O 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


11 cards
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
  .gitignore

Separa 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.js

O 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 vitest

O Vitest (ou Jest) testa funções isoladamente. O describe agrupa testes e o expect verifica resultados. Corre com npx vitest.

Avançado


11 cards
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ária

O 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.