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);
Practica esta lección
Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.
Empezar a practicar