Pular para o conteúdo
TypeScript

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. 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 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.

  1. Passo 1 - Entenda os generics do Request: Request. Params tipa req.params, ReqBody tipa req.body, ReqQuery tipa req.query. Passe interfaces específicas pra cada um.
  2. Passo 2 - Crie interfaces pro body: Defina uma interface com os campos exatos que o endpoint espera. Use em Request<{}, {}, SuaInterface>. Agora req.body tem autocomplete e validação de tipo.
  3. Passo 3 - Tipe params da URL: Crie interface com os nomes dos parâmetros da rota. /users/:id vira { id: string }. Use em Request.
  4. Passo 4 - Tipe query string: Query params são sempre string. Crie interface com campos opcionais: { page?: string; limit?: string }. Use no quarto generic do Request.
  5. Passo 5 - Estenda Request globalmente: Crie um arquivo .d.ts com declare global pra adicionar propriedades que middleware injeta. Ideal pra userId, role, tenantId.
  6. Passo 6 - Tipe Response com generics: Response garante que res.json() só aceita o formato correto. Crie tipos padronizados de resposta que todo endpoint usa.

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. 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

  • Body tipado com interface específica em handlers POST/PUT/PATCH
  • Params tipados com interface que reflete a rota (/users/:id = { id: string })
  • Query tipada com campos opcionais (tudo é string no query)
  • Arquivo .d.ts criado com declare global pra estender Request
  • export {} no final do arquivo .d.ts
  • Response tipado com generics pra manter contrato de API
  • Helper type TypedRequest criado pra reduzir boilerplate
  • Validação runtime com Zod ou Joi em todo body de entrada
  • Conversão de query params de string pra number quando necessário

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á.