Rotas e Middlewares
Middlewares são o coração do Express. Nesta aula, vamos entender profundamente como funcionam e criar middlewares personalizados para nossa API.
O que são Middlewares?
Middlewares são funções que têm acesso ao objeto request, response e à função next. Eles formam uma cadeia de processamento:
Request → Middleware 1 → Middleware 2 → ... → Rota → Response
Anatomia de um Middleware
import { Request, Response, NextFunction } from 'express'; function meuMiddleware(req: Request, res: Response, next: NextFunction) { // 1. Executar lógica console.log(`${req.method} ${req.path}`); // 2. Modificar req ou res (opcional) req.customProperty = 'valor'; // 3. Passar para o próximo ou finalizar next(); // Continua para o próximo middleware // ou res.status(401).json({ error: 'Não autorizado' }); // Finaliza aqui}Tipos de Middlewares
1. Middleware de Aplicação
Executa em todas as requisições:
// Executa em TODAS as rotasapp.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next();});2. Middleware de Rota
Executa apenas em rotas específicas:
// Apenas em rotas que começam com /apiapp.use('/api', (req, res, next) => { console.log('Requisição na API'); next();}); // Apenas em uma rota específicaapp.get('/users/:id', verificaPermissao, (req, res) => { // ...});3. Middleware de Erro
Tem 4 parâmetros e captura erros:
app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).json({ error: 'Algo deu errado!' });});Middlewares Personalizados
Logger Middleware
// src/middlewares/logger.middleware.tsimport { Request, Response, NextFunction } from 'express'; export function loggerMiddleware(req: Request, res: Response, next: NextFunction) { const start = Date.now(); // Quando a resposta terminar, loga o tempo res.on('finish', () => { const duration = Date.now() - start; console.log( `[${new Date().toISOString()}] ${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms` ); }); next();}Request ID Middleware
Adiciona um ID único a cada requisição para rastreamento:
// src/middlewares/requestId.middleware.tsimport { Request, Response, NextFunction } from 'express';import { randomUUID } from 'crypto'; // Extender tipo Requestdeclare global { namespace Express { interface Request { requestId: string; } }} export function requestIdMiddleware(req: Request, res: Response, next: NextFunction) { const requestId = req.headers['x-request-id'] as string || randomUUID(); req.requestId = requestId; res.setHeader('x-request-id', requestId); next();}Rate Limiter Middleware
Limita requisições por IP:
// src/middlewares/rateLimiter.middleware.tsimport { Request, Response, NextFunction } from 'express'; interface RateLimitStore { [ip: string]: { count: number; resetTime: number; };} const store: RateLimitStore = {}; interface RateLimiterOptions { windowMs: number; // Janela de tempo em ms max: number; // Máximo de requisições} export function rateLimiter(options: RateLimiterOptions) { const { windowMs, max } = options; return (req: Request, res: Response, next: NextFunction) => { const ip = req.ip || req.socket.remoteAddress || 'unknown'; const now = Date.now(); // Inicializa ou reseta contador if (!store[ip] || now > store[ip].resetTime) { store[ip] = { count: 1, resetTime: now + windowMs, }; return next(); } // Incrementa contador store[ip].count++; // Verifica limite if (store[ip].count > max) { const retryAfter = Math.ceil((store[ip].resetTime - now) / 1000); res.setHeader('Retry-After', retryAfter); res.setHeader('X-RateLimit-Limit', max); res.setHeader('X-RateLimit-Remaining', 0); return res.status(429).json({ error: 'Too Many Requests', message: `Limite de ${max} requisições excedido. Tente novamente em ${retryAfter}s`, }); } // Headers informativos res.setHeader('X-RateLimit-Limit', max); res.setHeader('X-RateLimit-Remaining', max - store[ip].count); next(); };}Uso:
// Limita a 100 requisições por minutoapp.use('/api', rateLimiter({ windowMs: 60000, max: 100 })); // Limita login a 5 tentativas por minutoapp.use('/api/auth/login', rateLimiter({ windowMs: 60000, max: 5 }));Validação de Content-Type
// src/middlewares/contentType.middleware.tsimport { Request, Response, NextFunction } from 'express'; export function requireJson(req: Request, res: Response, next: NextFunction) { if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') { const contentType = req.headers['content-type']; if (!contentType || !contentType.includes('application/json')) { return res.status(415).json({ error: 'Unsupported Media Type', message: 'Content-Type deve ser application/json', }); } } next();}Async Middlewares
Para middlewares assíncronos, precisamos tratar erros:
// Wrapper para async middlewaresexport function asyncHandler( fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); };} // Usoapp.get('/users', asyncHandler(async (req, res) => { const users = await userService.findAll(); res.json(users);}));Ordem dos Middlewares
A ordem importa! Middlewares executam na ordem em que são definidos:
const app = express(); // 1. Primeiro - Headers de segurançaapp.use(helmet()); // 2. CORSapp.use(cors()); // 3. Request ID (antes do logger)app.use(requestIdMiddleware); // 4. Loggerapp.use(loggerMiddleware); // 5. Rate Limiterapp.use(rateLimiter({ windowMs: 60000, max: 100 })); // 6. Body parsersapp.use(express.json());app.use(express.urlencoded({ extended: true })); // 7. Suas rotasapp.use('/api', routes); // 8. 404 handler (depois das rotas)app.use((req, res) => { res.status(404).json({ error: 'Rota não encontrada' });}); // 9. Error handler (sempre por último!)app.use(errorHandler);Rotas Avançadas
Rotas com Múltiplos Handlers
const checkAuth = (req: Request, res: Response, next: NextFunction) => { if (!req.headers.authorization) { return res.status(401).json({ error: 'Token não fornecido' }); } next();}; const checkAdmin = (req: Request, res: Response, next: NextFunction) => { if (req.user?.role !== 'admin') { return res.status(403).json({ error: 'Acesso negado' }); } next();}; // Encadeia múltiplos middlewaresrouter.delete('/users/:id', checkAuth, checkAdmin, deleteUser); // Ou usando arrayrouter.delete('/users/:id', [checkAuth, checkAdmin], deleteUser);Router com Prefixo e Middleware
// src/routes/admin.routes.tsimport { Router } from 'express';import { checkAuth, checkAdmin } from '../middlewares/auth.middleware'; const router = Router(); // Aplica middlewares a todas as rotas deste routerrouter.use(checkAuth);router.use(checkAdmin); router.get('/dashboard', (req, res) => { res.json({ message: 'Dashboard admin' });}); router.get('/users', (req, res) => { res.json({ message: 'Lista de usuários (admin)' });}); export default router;Rotas com Parâmetros Opcionais
// :format é opcionalrouter.get('/export/:format?', (req, res) => { const format = req.params.format || 'json'; // ...});Regex em Rotas
// Apenas números no IDrouter.get('/users/:id(\\d+)', (req, res) => { // req.params.id é garantido ser numérico}); // Slug alfanuméricorouter.get('/products/:slug([a-z0-9-]+)', (req, res) => { // ...});Middleware de Erro Global
// src/middlewares/errorHandler.middleware.tsimport { Request, Response, NextFunction } from 'express'; export class AppError extends Error { constructor( public statusCode: number, public message: string, public isOperational = true ) { super(message); Object.setPrototypeOf(this, AppError.prototype); }} export function errorHandler( err: Error | AppError, req: Request, res: Response, next: NextFunction) { // Log do erro console.error(`[ERROR] ${req.requestId || 'unknown'}:`, err); // AppError - erro operacional esperado if (err instanceof AppError) { return res.status(err.statusCode).json({ success: false, message: err.message, }); } // Erro de validação do Express if (err.name === 'ValidationError') { return res.status(400).json({ success: false, message: 'Dados inválidos', errors: err.message, }); } // Erro desconhecido - não expõe detalhes em produção const message = process.env.NODE_ENV === 'production' ? 'Erro interno do servidor' : err.message; res.status(500).json({ success: false, message, ...(process.env.NODE_ENV !== 'production' && { stack: err.stack }), });}Usando em rotas:
import { AppError } from '../middlewares/errorHandler.middleware'; router.get('/users/:id', asyncHandler(async (req, res) => { const user = await userService.findById(req.params.id); if (!user) { throw new AppError(404, 'Usuário não encontrado'); } res.json(user);}));Resumo
Nesta aula aprendemos:
- ✅ Como funcionam middlewares no Express
- ✅ Criar middlewares personalizados (logger, rate limiter, etc)
- ✅ Tratar middlewares assíncronos
- ✅ Ordem correta dos middlewares
- ✅ Rotas avançadas com múltiplos handlers
- ✅ Error handler global
Na próxima aula, vamos organizar nosso código com Arquitetura MVC! 🏗️