Trabalhar com Node + Express
Modelo mental e estrutura
Resumo de campo para montar e manter uma API com Express sem virar espaguete.
A ideia central: Express é um roteador minimalista construído sobre uma pilha de
middlewares — toda requisição percorre app → router → middlewares → handler, e
cada peça só chama next() ou responde. Para a ordem dessa pilha, ver
cadeia de middlewares; aqui é o fluxo de trabalho ao redor.
O modelo mental: req → middlewares → res
Cada middleware é uma função (req, res, next). Ele faz seu pedaço e:
- chama
next()para passar adiante, - ou responde (
res.json()) e encerra a cadeia, - ou chama
next(err)para pular direto ao error handler.
import express, { Request, Response, NextFunction } from "express";
const app = express();
app.use(express.json()); // parseia o body JSON em todas as rotas.
app.get("/health", (_req: Request, res: Response) => {
res.json({ status: "ok" }); // encerra a cadeia aqui.
});
⚠️ Esquecer next() (sem responder) trava a requisição: o cliente fica pendurado.
Estrutura de projeto: por camada
Separe responsabilidades para a base crescer sem acoplar tudo no arquivo de rota:
src/
routes/ # define endpoints e amarra middlewares (fino).
controllers/ # lê req, chama o service, formata a res.
services/ # regra de negócio pura (sem conhecer Express).
middlewares/ # auth, validação, rate limit — reutilizáveis.
app.ts # monta os middlewares globais e os routers.
- Router por recurso:
express.Router()agrupa rotas e mantémapp.tsenxuto. - ⚠️ Mantenha o service sem
req/res— assim ele é testável e reusável fora do HTTP.
// routes/orders.ts
import { Router } from "express";
const router = Router();
router.get("/", listOrders); // controller, não a lógica inline.
export default router;
// app.ts
app.use("/api/orders", router); // prefixo montado uma vez.
Async e tratamento de erros
Em handlers async, uma Promise rejeitada não é capturada automaticamente
(no Express 4) — você precisa encaminhá-la ao error handler.
// Wrapper que captura rejeições e chama next(err).
const wrap =
(fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>) =>
(req: Request, res: Response, next: NextFunction) =>
Promise.resolve(fn(req, res, next)).catch(next);
router.get(
"/:id",
wrap(async (req, res) => {
const order = await db.findOrder(req.params.id); // pode lançar.
res.json(order);
}),
);
// Error handler: SEMPRE 4 argumentos e por último na cadeia.
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
res.status(500).json({ error: err.message });
});
- ✅ Express 5 já encaminha rejeições de handlers async automaticamente — o
wrapvira opcional. Em projetos no 4.x, padronize o wrapper (ou useexpress-async-errors).
Tabela de referência rápida
| Quero… | Uso |
|---|---|
| Aplicar algo a todas as rotas | app.use(mw) |
| Agrupar rotas de um recurso | express.Router() + app.use(prefixo, r) |
| Parsear JSON do body | express.json() |
| Capturar erro de handler async | wrapper .catch(next) (ou Express 5) |
| Tratar todos os erros | middleware de 4 args, por último |
| Validar entrada | zod / Joi num middleware antes do handler |
Armadilhas comuns
- Ordem importa: body parser e auth antes das rotas que dependem deles — ver ordem dos middlewares.
- Não bloqueie o event loop com trabalho de CPU dentro do handler — ver alta concorrência.
- Sempre valide e sanitize
req.body/req.paramsantes de usar. - Helmet + CORS desde o primeiro commit; segurança não é refactor de depois.
Referências e ferramentas
- express — o framework; minimalista de propósito.
- zod — validação de schema tipada para body/query/params.
- helmet — headers de segurança HTTP em uma linha.
- morgan — logging de requisições para dev.
Relacionadas: cadeia de middlewares · alta concorrência · padrões TypeScript.