
Desvendando o Express: middleware em profundidade — composição, ordem e padrões (na prática)
Resumo rápido: middleware é o coração do Express. Ele recebe o req, pode enriquecer o res, decidir se responde ou chama next(). Aqui você aprende ordem de execução, tipos de middleware (de app, de roteador, de erro), padrões úteis (logger, validação, auth, rate limit) e como evitar armadilhas com assíncronos.
O que é middleware (e por que você deve se importar)
Um middleware é uma função (req, res, next) que:
- Lê/modifica
reqeres; - Encaminha o fluxo com
next()ou encerra a resposta; - Pode gerar erros chamando
next(err).
A ordem importa: middlewares são executados na sequência em que foram registrados.
Setup mínimo
mkdir express-mw && cd express-mw
npm init -y
npm i express express-rate-limit helmet cors
npm i -D nodemon
package.json (trecho):
{
"type": "module",
"scripts": { "dev": "nodemon src/server.js" },
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.0",
"express-rate-limit": "^7.0.0",
"helmet": "^7.0.0"
}
}
Estrutura:
src/
server.js
app.js
middlewares/
logger.js
validate.js
auth.js
routes/
index.js
users.js
Tipos de middleware que você usará no dia a dia
1) De aplicação (global)
Aplicado ao app inteiro, antes das rotas.
// src/app.js
import express from "express";
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
import logger from "./middlewares/logger.js";
import indexRouter from "./routes/index.js";
import usersRouter from "./routes/users.js";
const app = express();
// Segurança e hardening
app.use(helmet());
// CORS (ajuste origin conforme seu front)
app.use(cors());
// Limite de requisições (protege sua API)
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// Parsers
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Logger custom
app.use(logger);
// Rotas
app.use("/", indexRouter);
app.use("/users", usersRouter);
// 404
app.use((req, res) => res.status(404).json({ error: "Rota não encontrada" }));
// Handler central de erros
app.use((err, req, res, next) => {
console.error("[ERRO]", err);
res.status(err.status || 500).json({ error: err.message || "Erro interno" });
});
export default app;
2) De roteador (escopo menor)
Limitado a um conjunto de rotas — ótimo para regras específicas.
// src/routes/users.js
import { Router } from "express";
import { requireAuth } from "../middlewares/auth.js";
import { validateBody } from "../middlewares/validate.js";
const router = Router();
// Exige auth e validação só neste grupo
router.use(requireAuth);
router.get("/", (req, res) => {
res.json([{ id: 1, name: "Ada" }]);
});
router.post(
"/",
validateBody({ required: ["name"] }),
(req, res) => res.status(201).json({ id: 2, name: req.body.name })
);
export default router;
3) De erro (quatro parâmetros)
Detecta falhas e padroniza a resposta.
// Assinatura: (err, req, res, next)
// Já incluímos um handler central no app; você pode ter handlers especializados por domínio se necessário.
Padrões de middleware prontos para usar
Logger de requests
// src/middlewares/logger.js
export default function logger(req, res, next) {
const t0 = Date.now();
res.on("finish", () => {
const ms = Date.now() - t0;
console.log(`${req.method} ${req.originalUrl} → ${res.statusCode} (${ms}ms)`);
});
next();
}
Validação de corpo enxuta
// src/middlewares/validate.js
export function validateBody({ required = [] } = {}) {
return (req, res, next) => {
const missing = required.filter((k) => !(k in req.body));
if (missing.length) return next({ status: 400, message: `Campos obrigatórios: ${missing.join(", ")}` });
next();
};
}
Autenticação simples (mock)
// src/middlewares/auth.js
export function requireAuth(req, res, next) {
const token = req.get("Authorization")?.replace("Bearer ", "");
if (!token || token !== "seu_token_aqui") {
return next({ status: 401, message: "Não autorizado" });
}
// Ex.: anexar usuário ao req
req.user = { id: "123", role: "admin" };
next();
}
Armadilhas com assíncronos (e como evitar)
Problema comum: lançar erro dentro de async sem capturar — a execução “some” e o handler central não recebe o erro.
Solução 1 — try/catch + next(err):
router.get("/:id", async (req, res, next) => {
try {
const user = await buscarUsuario(req.params.id); // I/O
if (!user) return next({ status: 404, message: "Usuário não encontrado" });
res.json(user);
} catch (err) {
next(err);
}
});
Solução 2 — wrapper utilitário:
const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
router.get("/:id", wrap(async (req, res) => {
const user = await buscarUsuario(req.params.id);
if (!user) throw { status: 404, message: "Usuário não encontrado" };
res.json(user);
}));
Ordem, escopo e composição: três regras de ouro
- Registre parsers e segurança primeiro, antes das rotas que dependem deles.
- Aplique middlewares no menor escopo possível (de roteador) para manter performance e clareza.
- Padronize erros: tudo que falhar cai no mesmo handler de erro.
FAQ rápido
1) Devo usar um middleware “global” para tudo?
Não. Prefira escopo de roteador para regras específicas, mantendo o global só para cross-cutting (segurança, parsers, log).
2) Como retornar erros customizados?
Crie objetos { status, message } e passe a next(err); o handler central converte em resposta JSON.
3) Como lidar com limites e segurança?
Combine helmet, cors (origem controlada) e express-rate-limit. Para auth, use tokens (JWT, por exemplo) e renove chaves com regularidade.