Inicio > Cursos > TypeScript > Express y APIs Tipadas
TypeScriptTypeScript en PrácticaLección 15 de 18

Express y APIs Tipadas

Express con TypeScript requiere tipar los objetos Request y Response para que el compilador verifique que accedes solo a datos que existen. El tipo genérico de Request acepta cuatro parámetros: Request<Params, ResBody, ReqBody, QueryString>. Esto permite anotar exactamente qué espera cada ruta: los parámetros de la URL (:id), el body del POST/PUT, los query params, y el tipo de la respuesta que enviará res.json().

Un desafío habitual es que los middlewares de autenticación agregan propiedades al objeto Request (como req.usuario) que Express no conoce por defecto. La solución es la augmentación de namespace: declarar en un archivo .d.ts que el namespace de Express extiende su interfaz Request con las propiedades que agregas. TypeScript fusiona ambas declaraciones automáticamente. Esto evita tener que hacer casts manuales en cada controlador para acceder a req.usuario.

import { Request, Response, NextFunction, Router } from "express";

// Tipar Request con parámetros, body, query y respuesta
interface CreateUserBody { nombre: string; email: string; }
interface UserParams { id: string; }
type UserQuery = { includeDeleted?: string };

type CreateUserReq = Request<{}, {}, CreateUserBody>;
type GetUserReq = Request<UserParams, {}, {}, UserQuery>;

// Wrapper de respuesta tipado
interface ApiResponse<T> { data: T; message?: string; }
type ApiRes<T> = Response<ApiResponse<T>>;

// Controller tipado
async function createUser(req: CreateUserReq, res: ApiRes<Usuario>): Promise<void> {
  const { nombre, email } = req.body;
  const usuario = await usuarioService.crear({ nombre, email });
  res.status(201).json({ data: usuario });
}

// Augmentación de namespace Express para usuario autenticado
declare global {
  namespace Express {
    interface Request {
      usuario?: { id: string; email: string; rol: string };
    }
  }
}

// Middleware de autenticación tipado
async function authMiddleware(req: Request, res: Response, next: NextFunction): Promise<void> {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) { res.status(401).json({ error: "No autorizado" }); return; }
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
    req.usuario = { id: payload.id, email: payload.email, rol: payload.rol };
    next();
  } catch { res.status(401).json({ error: "Token inválido" }); }
}

// Router tipado
const router = Router();
router.post("/", createUser);
router.get("/:id", authMiddleware, getUser);
Consejo🔑 El ! (non-null assertion) en process.env.JWT_SECRET! le dice a TypeScript que garantizas que no es undefined. Mejor aún: valida todas las env vars al iniciar con zod.parse(process.env).
← Hooks y Context TipadosError Handling Tipado →

Practica esta lección

Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.

Empezar a practicar