Pular para o conteúdoPedro Farbo
Lição 4 / 2550 min

Rotas e Middlewares

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

typescript
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:

typescript
// 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:

typescript
// 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:

typescript
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

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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

typescript
// 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:

typescript
// 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:

typescript
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

typescript
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

typescript
// 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

typescript
// :format é opcionalrouter.get('/export/:format?', (req, res) => {  const format = req.params.format || 'json';  // ...});

Regex em Rotas

typescript
// 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

typescript
// 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:

typescript
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! 🏗️

Gostou do conteúdo? Sua contribuição ajuda a manter tudo online e gratuito!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e