Tipar Request e Response no Express 2026
Estenda a interface Request, tipe body, params e query com generics, crie respostas padronizadas e elimine any dos seus endpoints Express.
Por que isso é importante
Tipar Request e Response no Express 2026. Estenda a interface Request, tipe body, params e query com generics, crie respostas padronizadas e elimine any dos seus endpoints Express.
Como Funciona Request e Response no Express Tipado
No Express, o objeto Request carrega tudo que o cliente envia: body, params da URL, query string, headers, cookies. Já o Response é o que você manda de volta: JSON, status code, headers de resposta.
O @types/express define Request como um generic com 4 parâmetros: Request<Params, ResBody, ReqBody, ReqQuery>. Cada um desses parâmetros é um tipo que você pode customizar. Por padrão, todos são genéricos e pouco úteis. A mágica acontece quando você substitui esses defaults pelos tipos do seu domínio.
Response também aceita generics. Response<ResBody> define o formato do JSON que seu endpoint retorna. Quando você tipa isso, o res.json() só aceita dados no formato correto. Se alguém muda o contrato da API sem atualizar o handler, o compilador reclama na hora.
A combinação de Request e Response tipados cria um contrato completo: o que entra, o que sai. Isso é documentação viva que o compilador valida. Galera que trabalha com equipes grandes sabe o quanto isso economiza tempo de debug.
Passo a Passo: Tipando Request e Response
Vamos construir a tipagem completa do zero. Cada passo adiciona uma camada de segurança.
Exemplos Práticos: Request e Response Tipados
Chega de teoria. Vamos ver código real com cada tipo de tipagem.
Tipando Body: POST e PUT
// Interfaces do domínio
interface CreateProductBody {
name: string;
price: number;
category: string;
stock: number;
}
interface UpdateProductBody {
name?: string;
price?: number;
category?: string;
stock?: number;
}
// POST - body completo
app.post('/products',
(req: Request<{}, {}, CreateProductBody>, res: Response) => {
const { name, price, category, stock } = req.body;
// Todos os campos com tipo correto e autocomplete
console.log(name.toUpperCase()); // OK: name é string
console.log(price.toFixed(2)); // OK: price é number
}
);
// PUT - body parcial
app.put('/products/:id',
(req: Request<{ id: string }, {}, UpdateProductBody>, res: Response) => {
const { name, price } = req.body;
// name e price podem ser undefined (campos opcionais)
if (name) console.log(name.toUpperCase());
}
);
Tipando Params e Query String
// Params da rota
interface ProductParams {
id: string;
categorySlug: string;
}
// Query string
interface ProductQuery {
page?: string;
limit?: string;
sort?: 'price' | 'name' | 'createdAt';
order?: 'asc' | 'desc';
}
// Rota com params + query tipados
app.get('/categories/:categorySlug/products/:id',
(req: Request<ProductParams, {}, {}, ProductQuery>, res: Response) => {
const { id, categorySlug } = req.params;
const { page, limit, sort, order } = req.query;
// sort só aceita 'price' | 'name' | 'createdAt'
// order só aceita 'asc' | 'desc'
const pageNum = parseInt(page || '1', 10);
const limitNum = parseInt(limit || '20', 10);
res.json({ id, categorySlug, pageNum, limitNum, sort, order });
}
);
Estendendo Request com Dados de Autenticação
// src/@types/express/index.d.ts
declare global {
namespace Express {
interface Request {
userId?: string;
userRole?: 'admin' | 'editor' | 'viewer';
tenantId?: string;
permissions?: string[];
}
}
}
export {}; // Necessário pra transformar em módulo
// Middleware de autenticação injeta os dados
app.use('/admin', (req: Request, res: Response, next) => {
const decoded = verifyToken(req.headers.authorization);
req.userId = decoded.sub;
req.userRole = decoded.role;
req.tenantId = decoded.tenantId;
next();
});
// Handler usa os dados com tipo correto
app.get('/admin/dashboard', (req: Request, res: Response) => {
// req.userId é string | undefined
// req.userRole é 'admin' | 'editor' | 'viewer' | undefined
if (req.userRole !== 'admin') {
return res.status(403).json({ error: 'Acesso negado' });
}
res.json({ userId: req.userId, dashboard: '...' });
});
Response Padronizado com Generics
// Tipo de resposta padrão da API
interface ApiSuccess<T> {
success: true;
data: T;
meta?: {
page: number;
total: number;
limit: number;
};
}
interface ApiError {
success: false;
error: string;
code: number;
}
type ApiResponse<T> = ApiSuccess<T> | ApiError;
// Produto tipado
interface Product {
id: string;
name: string;
price: number;
}
// Response tipado garante formato consistente
app.get('/products/:id',
(req: Request<{ id: string }>, res: Response<ApiResponse<Product>>) => {
const product = findProduct(req.params.id);
if (!product) {
// O compilador valida: precisa ter success, error e code
return res.status(404).json({
success: false,
error: 'Produto nao encontrado',
code: 404
});
}
// Também valida: precisa ter success e data do tipo Product
res.json({
success: true,
data: product
});
}
);
Helper Type: Request Customizado Reutilizável
// Tipo auxiliar que simplifica a declaração
type TypedRequest<
TBody = {},
TParams = {},
TQuery = {}
> = Request<TParams, {}, TBody, TQuery>;
// Agora os handlers ficam mais limpos
app.post('/orders',
(req: TypedRequest<CreateOrderBody>, res: Response) => {
const { items, address } = req.body;
// Limpo e tipado
}
);
app.get('/orders/:id',
(req: TypedRequest<{}, { id: string }, { include?: string }>, res: Response) => {
const { id } = req.params;
const { include } = req.query;
// Tudo tipado com helper
}
);
A sacada desse helper type é que ele inverte a ordem dos generics pra colocar body primeiro (que é o mais usado). Isso reduz boilerplate e mantém o código legível.
Erros Comuns ao Tipar Request e Response
Cuidados que evitam horas de debug
Confiar na tipagem sem validar runtime: TypeScript só checa em compilação. O body pode vir completamente diferente do que a interface declara. Sempre valide com Zod, Joi ou class-validator antes de confiar nos dados.
Esquecer o export {} no arquivo .d.ts: sem isso, o TypeScript não trata o arquivo como módulo e o declare global não funciona. Adicione export {} no final do arquivo de declaração.
Passar tipos na ordem errada dos generics: Request<Params, ResBody, ReqBody, ReqQuery>. Galera troca Params com ReqBody o tempo todo. Se seu body tá vindo como params, confira a ordem.
Não tipar query como string: query params sempre chegam como string, mesmo que representem números. Use parseInt() ou Number() pra converter. Não declare como number na interface de query.
Tipar Response mas não tratar todos os casos: se sua resposta é ApiSuccess | ApiError, o compilador quer que ambos os caminhos retornem o formato correto. Não pule o caso de erro.
Checklist de Request e Response Tipados
Construa APIs Tipadas de Verdade
Tipar Request e Response é o que separa um backend amador de um profissional. No CrazyStack, você constrói uma API completa usando essas técnicas num projeto real: SaaS com autenticação, CRUD completo, validação com Zod e deploy em produção.
Se você quer dominar Express com TypeScript e entregar endpoints que o time confia, esse projeto te leva até lá.
Continue lendo
Como Tipar Express com TypeScript: Guia Completo
Instale @types/express, configure tipos e organize seu servidor Express com TypeScript.
Como Tipar Middleware no Express com TypeScript
Domine RequestHandler, error middleware e async middleware tipados no Express.
Como Usar Zod com TypeScript para Validação
Valide dados em runtime e gere tipos TypeScript automaticamente com Zod.
Interface no TypeScript
Quando usar interface