Pular para o conteúdo
TypeScript

Tipar Middleware no Express com TypeScript

Domine RequestHandler, error middleware, async middleware e middleware de autenticação com tipagem completa no Express. Exemplos reais e prontos pra usar.

Por que isso é importante

Tipar Middleware no Express com TypeScript. Domine RequestHandler, error middleware, async middleware e middleware de autenticação com tipagem completa no Express. Exemplos reais e prontos pra usar.

Como Middleware Funciona no Express Tipado

Middleware no Express é uma função que recebe req, res e next. Ela pode modificar o request, enviar uma resposta ou chamar next() pra passar pro próximo middleware da cadeia. Quando você usa TypeScript, cada uma dessas funções precisa de tipo correto.

O @types/express exporta dois tipos principais pra middleware: RequestHandler e ErrorRequestHandler. RequestHandler é pra middleware normal (3 argumentos: req, res, next). ErrorRequestHandler é pra middleware de erro (4 argumentos: err, req, res, next). Usar o tipo certo garante que o Express identifica corretamente o tipo de middleware.

A diferença é sutil mas importante. Express diferencia middleware de erro pelo número de argumentos. Se sua função tem 4 parâmetros, é middleware de erro. Se tem 3, é middleware normal. TypeScript garante essa distinção em tempo de compilação. Galera que mistura os dois tipos acaba com middleware que nunca é chamado.

Outro ponto: middleware async. Express não captura erros de promises rejeitadas nativamente. Você precisa de um wrapper ou usar express-async-errors. Tipar isso corretamente evita que erros async passem despercebidos.

Passo a Passo: Tipando Middleware no Express

Vamos construir cada tipo de middleware com tipagem completa.

  1. Passo 1 - Use RequestHandler pra middleware simples: Importe RequestHandler de 'express'. Tipe sua função como RequestHandler. O compilador garante que req, res e next têm os tipos corretos.
  2. Passo 2 - Use ErrorRequestHandler pra middleware de erro: Middleware de erro SEMPRE tem 4 parâmetros (err, req, res, next). Use ErrorRequestHandler pra garantir isso. Coloque no final da cadeia de middleware.
  3. Passo 3 - Crie middleware de autenticação tipado: Estenda Request com propriedades de auth. O middleware decodifica o token e injeta dados no req. Handlers seguintes usam esses dados com tipos corretos.
  4. Passo 4 - Tipe middleware de validação: Middleware que valida body pode usar generics pra garantir que o próximo handler recebe dados já validados e tipados.
  5. Passo 5 - Crie wrapper pra async middleware: Crie uma função que envolve o middleware async e captura erros automaticamente. Tipe essa função pra manter segurança de tipos.
  6. Passo 6 - Organize middleware em módulos: Separe middleware por responsabilidade: auth, validation, logging, error. Cada módulo exporta middleware tipado.

Exemplos Práticos: Middleware Tipado no Express

Vamos direto pro código. Cada tipo de middleware com tipagem real.

Middleware Básico com RequestHandler

import { RequestHandler, ErrorRequestHandler } from 'express';

// Middleware de logging tipado
const loggerMiddleware: RequestHandler = (req, res, next) => {
  const start = Date.now();
  console.log(`[${req.method}] ${req.path}`);

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`[${req.method}] ${req.path} - ${res.statusCode} (${duration}ms)`);
  });

  next();
};

// Middleware de CORS tipado
const corsMiddleware: RequestHandler = (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
};

app.use(loggerMiddleware);
app.use(corsMiddleware);

Middleware de Autenticação com Tipos Custom

import { RequestHandler } from 'express';
import jwt from 'jsonwebtoken';

// Tipo do payload do token
interface TokenPayload {
  sub: string;
  role: 'admin' | 'user' | 'editor';
  tenantId: string;
}

// Middleware de autenticação
const authMiddleware: RequestHandler = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Token nao fornecido' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as TokenPayload;

    // Injeta dados tipados no request
    req.userId = decoded.sub;
    req.userRole = decoded.role;
    req.tenantId = decoded.tenantId;

    next();
  } catch {
    return res.status(401).json({ error: 'Token invalido' });
  }
};

// Middleware de autorização por role
const requireRole = (...roles: Array<'admin' | 'user' | 'editor'>): RequestHandler => {
  return (req, res, next) => {
    if (!req.userRole || !roles.includes(req.userRole)) {
      return res.status(403).json({ error: 'Sem permissao' });
    }
    next();
  };
};

// Uso:
app.use('/api', authMiddleware);
app.delete('/api/users/:id', requireRole('admin'), deleteUserHandler);

Error Middleware com ErrorRequestHandler

import { ErrorRequestHandler } from 'express';

// Erro customizado
class AppError extends Error {
  constructor(
    public message: string,
    public statusCode: number = 500,
    public code?: string
  ) {
    super(message);
    this.name = 'AppError';
  }
}

// Middleware de erro global - PRECISA de 4 parâmetros
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  console.error(`[ERROR] ${req.method} ${req.path}:`, err.message);

  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: err.message,
      code: err.code,
    });
  }

  // Erros do Prisma
  if (err.code === 'P2002') {
    return res.status(409).json({
      error: 'Registro duplicado',
      code: 'DUPLICATE_ENTRY',
    });
  }

  if (err.code === 'P2025') {
    return res.status(404).json({
      error: 'Registro nao encontrado',
      code: 'NOT_FOUND',
    });
  }

  // Erro genérico
  res.status(500).json({
    error: 'Erro interno do servidor',
    code: 'INTERNAL_ERROR',
  });
};

// IMPORTANTE: registrar no final, depois de todas as rotas
app.use(errorHandler);

Async Middleware com Wrapper Tipado

import { RequestHandler, Request, Response, NextFunction } from 'express';

// Wrapper que captura erros de promises
const asyncHandler = (fn: RequestHandler): RequestHandler => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Agora qualquer middleware async tem erros capturados
const getUserHandler = asyncHandler(async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: req.params.id },
  });

  if (!user) {
    throw new AppError('Usuario nao encontrado', 404);
  }

  res.json({ data: user });
  // Se o findUnique lançar erro, o wrapper captura e passa pro errorHandler
});

app.get('/users/:id', getUserHandler);

Middleware de Validação com Zod

import { RequestHandler } from 'express';
import { z, ZodSchema } from 'zod';

// Middleware factory de validação
const validate = (schema: ZodSchema): RequestHandler => {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      return res.status(400).json({
        error: 'Dados invalidos',
        details: result.error.flatten().fieldErrors,
      });
    }

    req.body = result.data; // Body agora é validado e tipado
    next();
  };
};

// Schema de validação
const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  password: z.string().min(8),
});

// Uso: middleware valida antes do handler executar
app.post('/users',
  validate(createUserSchema),
  asyncHandler(async (req, res) => {
    // req.body já está validado
    const user = await createUser(req.body);
    res.status(201).json({ data: user });
  })
);

Percebe como cada middleware tem responsabilidade clara? Auth verifica token, validate checa body, asyncHandler captura erros, errorHandler trata tudo. Cada um com tipo correto. A cadeia inteira fica previsível.

Erros Comuns ao Tipar Middleware

Pegadinhas que quebram middleware silenciosamente

Middleware de erro com 3 parâmetros: Express usa o número de argumentos pra distinguir middleware normal de erro. Se você escreve (err, req, res) sem o next, o Express trata como middleware normal e ignora. SEMPRE inclua os 4 parâmetros, mesmo que não use o next.

Não chamar next() em middleware normal: se você não chama next() e não envia resposta, a request fica pendurada até dar timeout. O TypeScript não avisa sobre isso. Crie um lint rule ou revise manualmente.

Registrar errorHandler antes das rotas: middleware de erro precisa ficar DEPOIS de todas as rotas e outros middleware. Se ficar antes, não pega nenhum erro das rotas registradas depois.

Esquecer de capturar erros async: Express não captura reject de promise automaticamente. Sem asyncHandler, um throw dentro de async middleware mata o processo. Use o wrapper ou instale express-async-errors.

Tipar middleware factory sem retornar RequestHandler: quando você cria uma função que retorna middleware (como requireRole), o retorno precisa ser tipado como RequestHandler. Sem isso, o TypeScript aceita qualquer coisa.

Checklist de Middleware Tipado

  • Middleware normal usa RequestHandler com 3 parâmetros
  • Middleware de erro usa ErrorRequestHandler com 4 parâmetros
  • Error middleware registrado DEPOIS de todas as rotas
  • asyncHandler wrapper criado pra capturar erros de promises
  • Middleware de autenticação injeta dados tipados no Request
  • Middleware de validação usa Zod ou Joi antes dos handlers
  • Middleware factories retornam RequestHandler explicitamente
  • Todos os middleware chamam next() ou enviam resposta

Middleware Profissional na Prática

Middleware tipado é o que separa uma API amadora de uma profissional. No CrazyStack, você constrói toda a camada de middleware de um SaaS real: autenticação JWT, validação com Zod, tratamento de erros, rate limiting. Tudo tipado e testado.

Se você quer construir APIs que aguentam tráfego real e são fáceis de manter, esse projeto te leva do zero ao deploy.