O ciclo requisição-resposta no Express
No Express, cada requisição HTTP percorre uma cadeia previsível: middlewares (pré-processamento), roteamento (seleção da rota), handlers (a lógica da rota) e, por fim, a resposta. Entender esse fluxo é essencial para organizar o back-end, aplicar validações, autenticação, logs e padronizar respostas.
- Middleware: função que recebe
(req, res, next)e pode ler/modificar a requisição, encerrar a resposta, ou delegar para o próximo passo comnext(). - Router: “sub-aplicação” com rotas agrupadas (ex.:
/api/v1,/health). - Handler: função final da rota (ou uma das funções) que normalmente produz a resposta.
- Error middleware: middleware especial com assinatura
(err, req, res, next), executado quando ocorre erro ou quandonext(err)é chamado.
Ordem importa
O Express executa middlewares e rotas na ordem em que você registra com app.use e app.METHOD. Isso afeta desde parsing de JSON até autenticação e tratamento de 404.
Projeto base: Express + TypeScript com roteamento organizado
Estrutura sugerida
Uma organização simples e escalável para rotas e middlewares:
src/ app.ts server.ts routes/ index.ts v1/ index.ts health.routes.ts echo.routes.ts middlewares/ requestId.ts notFound.ts errorHandler.ts types/ express.d.tsDependências
Instale Express e tipos:
npm i express npm i -D @types/express ts-node-devObservação: a configuração de TypeScript (tsconfig, build, etc.) já foi tratada anteriormente; aqui vamos focar no fluxo Express e tipagem.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Implementando o app e o servidor
src/app.ts: composição do pipeline
O app concentra middlewares globais e monta routers por prefixo.
import express, { type Request, type Response } from "express";import { routes } from "./routes";import { requestId } from "./middlewares/requestId";import { notFound } from "./middlewares/notFound";import { errorHandler } from "./middlewares/errorHandler";export const app = express();app.disable("x-powered-by");app.use(express.json());app.use(requestId);app.get("/", (_req: Request, res: Response) => { res.status(200).json({ ok: true, service: "api" });});app.use(routes);app.use(notFound);app.use(errorHandler);src/server.ts: inicialização
import { app } from "./app";const port = Number(process.env.PORT ?? 3000);app.listen(port, () => { console.log(`Listening on http://localhost:${port}`);});Middlewares: antes, durante e depois do handler
Middleware de correlação: requestId
Um padrão útil é atribuir um ID por requisição e devolvê-lo em header para rastreamento.
import { randomUUID } from "node:crypto";import type { Request, Response, NextFunction } from "express";export function requestId(req: Request, res: Response, next: NextFunction) { const id = req.header("x-request-id") ?? randomUUID(); res.setHeader("x-request-id", id); (req as any).requestId = id; next();}Mais adiante, vamos tipar isso corretamente com module augmentation para evitar any.
Middleware de 404: notFound
Se nenhuma rota respondeu, este middleware finaliza com 404.
import type { Request, Response, NextFunction } from "express";export function notFound(req: Request, res: Response, _next: NextFunction) { res.status(404).json({ error: "NOT_FOUND", message: `No route for ${req.method} ${req.path}`, });}Error handler: errorHandler
Centraliza erros e padroniza respostas. A assinatura com 4 parâmetros é obrigatória para o Express reconhecer como middleware de erro.
import type { Request, Response, NextFunction } from "express";export function errorHandler(err: unknown, _req: Request, res: Response, _next: NextFunction) { const message = err instanceof Error ? err.message : "Unexpected error"; res.status(500).json({ error: "INTERNAL_ERROR", message });}Roteadores e padrões consistentes (prefixos, composição e versionamento)
Router raiz: src/routes/index.ts
Este arquivo monta versões e grupos de rotas. Um padrão comum é /api/v1 para permitir evolução sem quebrar clientes.
import { Router } from "express";import { v1Routes } from "./v1";export const routes = Router();routes.use("/api/v1", v1Routes);Router da versão: src/routes/v1/index.ts
import { Router } from "express";import { healthRoutes } from "./health.routes";import { echoRoutes } from "./echo.routes";export const v1Routes = Router();v1Routes.use("/health", healthRoutes);v1Routes.use("/echo", echoRoutes);Rotas de healthcheck: health.routes.ts
import { Router, type Request, type Response } from "express";export const healthRoutes = Router();healthRoutes.get("/", (_req: Request, res: Response) => { res.status(200).json({ status: "up" });});Note que o endpoint final fica GET /api/v1/health por composição de prefixos.
Tipagem no Express: Request, Response e NextFunction
Por que tipar?
Sem tipagem, é fácil acessar req.params, req.query e req.body assumindo formatos incorretos. Com TypeScript, você consegue:
- Autocompletar e validação estática do formato esperado.
- Evitar
undefinede conversões implícitas. - Padronizar contratos de entrada/saída.
Genéricos do Request
O tipo Request do Express aceita genéricos para tipar parâmetros, resposta, body e query. A forma mais comum é:
Request<Params, ResBody, ReqBody, ReqQuery>Na prática, você frequentemente tipa Params, ReqBody e ReqQuery.
Tipando params com segurança
Exemplo: rota que recebe :name e devolve em header e body.
import { Router, type Request, type Response } from "express";export const echoRoutes = Router();type EchoParams = { name: string };echoRoutes.get("/:name", (req: Request<EchoParams>, res: Response) => { const { name } = req.params; res.setHeader("x-echo-name", name); res.status(200).json({ echoed: name });});Tipando query (e lidando com string vs number)
Query string chega como string (ou array de strings). Mesmo que você espere um número, você deve converter e validar.
type EchoQuery = { times?: string };echoRoutes.get("/:name/repeat", (req: Request<{ name: string }, any, any, EchoQuery>, res: Response) => { const timesRaw = req.query.times ?? "1"; const times = Number(timesRaw); if (!Number.isFinite(times) || times < 1 || times > 5) { return res.status(400).json({ error: "INVALID_QUERY", message: "times must be a number between 1 and 5", }); } const result = Array.from({ length: times }, () => req.params.name); res.status(200).json({ result });});Repare no padrão: tipar para saber o que pode existir e validar para garantir que o valor é aceitável.
Tipando body (com validação mínima)
O Express não valida JSON automaticamente; ele apenas faz parsing. Você deve validar o formato esperado.
type EchoBody = { message: string };echoRoutes.post("/", (req: Request<{}, any, EchoBody>, res: Response) => { const { message } = req.body; if (typeof message !== "string" || message.trim().length === 0) { return res.status(422).json({ error: "INVALID_BODY", message: "message is required", }); } res.status(201).json({ received: message });});Tipando propriedades adicionadas ao Request (module augmentation)
Se você adiciona requestId no middleware, o TypeScript não sabe disso por padrão. A forma correta é estender a interface Express.Request via declaration merging.
src/types/express.d.ts
declare global { namespace Express { interface Request { requestId?: string; } }}export {};Agora você pode usar req.requestId sem any:
echoRoutes.get("/:name", (req, res) => { res.status(200).json({ echoed: req.params.name, requestId: req.requestId });});Handlers assíncronos e propagação de erros
Em handlers async, erros devem chegar ao error handler. Em versões modernas do Express (5.x), erros lançados em async são encaminhados automaticamente; em Express 4.x, é comum usar try/catch e next(err) (ou um wrapper). Para manter o padrão explícito:
import type { Request, Response, NextFunction } from "express";echoRoutes.get("/async/fail", async (_req: Request, _res: Response, next: NextFunction) => { try { await Promise.reject(new Error("Boom")); } catch (err) { next(err); }});Mini-laboratório: observando headers, status codes e comportamento do Express
Objetivo: executar chamadas e observar como middlewares, roteamento e respostas se comportam em cenários diferentes. Use curl (ou qualquer cliente HTTP) e compare status, headers e body.
1) Verificando headers padrão e o x-request-id
Faça uma requisição e inspecione headers:
curl -i http://localhost:3000/api/v1/healthO que observar:
HTTP/1.1 200 OK- Header
x-request-idretornado pelo middleware - Ausência de
x-powered-by(desabilitado no app)
2) Enviando um x-request-id e verificando se ele é preservado
curl -i -H "x-request-id: lab-123" http://localhost:3000/api/v1/healthO que observar:
- O header de resposta
x-request-iddeve serlab-123
3) Testando 404 (rota inexistente) e formato de erro
curl -i http://localhost:3000/api/v1/does-not-existO que observar:
- Status
404 - Body JSON com
erroremessage - O 404 acontece porque nenhuma rota respondeu e o middleware
notFoundfinalizou a resposta
4) Testando validação de query e status code 400
curl -i "http://localhost:3000/api/v1/echo/ana/repeat?times=999"O que observar:
- Status
400 - Mensagem indicando regra de validação
5) Testando body JSON e status code 201 vs 422
Body válido:
curl -i -X POST http://localhost:3000/api/v1/echo -H "content-type: application/json" -d '{"message":"oi"}'Body inválido:
curl -i -X POST http://localhost:3000/api/v1/echo -H "content-type: application/json" -d '{"message":""}'O que observar:
201quando o body atende ao contrato422quando o formato é válido (JSON), mas o conteúdo não atende às regras
6) Forçando erro e verificando o error handler (500)
curl -i http://localhost:3000/api/v1/echo/async/failO que observar:
- Status
500 - Body padronizado pelo
errorHandler - O fluxo: handler chama
next(err)e o Express pula para o middleware de erro
Tabela rápida: onde tipar e o que validar
| Entrada | Onde fica | Como tipar | Validações comuns |
|---|---|---|---|
| Parâmetros de rota | req.params | Request<{ id: string }> | presença, formato (UUID), conversão para number quando necessário |
| Query string | req.query | Request<{}, any, any, { page?: string }> | conversão, limites, valores permitidos |
| Body JSON | req.body | Request<{}, any, { name: string }> | campos obrigatórios, tipos, regras de negócio |
| Headers | req.header() | tipagem indireta (string) | presença, normalização, fallback |