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.
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 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.
Continue lendo
Como Tipar Express com TypeScript: Guia Completo
Instale @types/express, configure tipos e organize seu servidor Express com TypeScript.
Como Tipar Request e Response no Express com TypeScript
Estenda Request, tipe body, params e query com generics no Express.
Como Tipar env com TypeScript
Valide e tipe variáveis de ambiente pra eliminar undefined do process.env.
Interface no TypeScript
Quando usar interface