
Configuración del proyecto
Para un proyecto Express con TypeScript, necesitas instalar las dependencias de tipos:
npm init -y
npm install express
npm install --save-dev typescript @types/node @types/express ts-node nodemon
Configuración recomendada en tsconfig.json para Node.js:
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Scripts recomendados en package.json:
{
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
Tipando Request y Response
Express proporciona tipos genéricos para Request y Response que permiten tipar los cuerpos de petición, parámetros de ruta, query strings y el cuerpo de respuesta:
import { Request, Response, NextFunction } from "express";
// Request<Params, ResBody, ReqBody, Query>
interface ParamsUsuario {
id: string;
}
interface BodyActualizarUsuario {
nombre?: string;
email?: string;
}
interface QueryBusqueda {
page?: string;
limit?: string;
buscar?: string;
}
// Ruta con todos los genéricos tipados
router.put(
"/usuarios/:id",
async (
req: Request<ParamsUsuario, unknown, BodyActualizarUsuario, QueryBusqueda>,
res: Response,
next: NextFunction
): Promise<void> => {
const { id } = req.params; // string
const { nombre, email } = req.body; // tipados correctamente
const { page } = req.query; // string | undefined
// Lógica del controlador
res.json({ id, nombre, email });
}
);
Estructura en capas: router, controlador, servicio
La arquitectura típica de una API Express en TypeScript separa las responsabilidades en capas:
Modelos e interfaces
// src/modelos/usuario.ts
export interface Usuario {
id: string;
nombre: string;
email: string;
rol: "admin" | "usuario";
creadoEn: Date;
}
export interface CrearUsuarioDto {
nombre: string;
email: string;
contrasena: string;
}
export interface ActualizarUsuarioDto {
nombre?: string;
email?: string;
}
Capa de servicio
// src/servicios/usuario.servicio.ts
import { Usuario, CrearUsuarioDto, ActualizarUsuarioDto } from "../modelos/usuario";
export class UsuarioServicio {
private usuarios: Map<string, Usuario> = new Map();
async crear(dto: CrearUsuarioDto): Promise<Usuario> {
const usuario: Usuario = {
id: crypto.randomUUID(),
nombre: dto.nombre,
email: dto.email,
rol: "usuario",
creadoEn: new Date()
};
this.usuarios.set(usuario.id, usuario);
return usuario;
}
async obtenerPorId(id: string): Promise<Usuario | null> {
return this.usuarios.get(id) ?? null;
}
async listar(): Promise<Usuario[]> {
return Array.from(this.usuarios.values());
}
async actualizar(id: string, dto: ActualizarUsuarioDto): Promise<Usuario | null> {
const usuario = this.usuarios.get(id);
if (!usuario) return null;
const actualizado: Usuario = { ...usuario, ...dto };
this.usuarios.set(id, actualizado);
return actualizado;
}
}
Capa de controlador
// src/controladores/usuario.controlador.ts
import { Request, Response, NextFunction } from "express";
import { UsuarioServicio } from "../servicios/usuario.servicio";
import { CrearUsuarioDto } from "../modelos/usuario";
export class UsuarioControlador {
constructor(private readonly servicio: UsuarioServicio) {}
listar = async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const usuarios = await this.servicio.listar();
res.json(usuarios);
} catch (error) {
next(error);
}
};
obtenerPorId = async (
req: Request<{ id: string }>,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const usuario = await this.servicio.obtenerPorId(req.params.id);
if (!usuario) {
res.status(404).json({ mensaje: "Usuario no encontrado" });
return;
}
res.json(usuario);
} catch (error) {
next(error);
}
};
crear = async (
req: Request<{}, {}, CrearUsuarioDto>,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const usuario = await this.servicio.crear(req.body);
res.status(201).json(usuario);
} catch (error) {
next(error);
}
};
}
Router
// src/rutas/usuario.rutas.ts
import { Router } from "express";
import { UsuarioControlador } from "../controladores/usuario.controlador";
import { UsuarioServicio } from "../servicios/usuario.servicio";
const router = Router();
const servicio = new UsuarioServicio();
const controlador = new UsuarioControlador(servicio);
router.get("/", controlador.listar);
router.get("/:id", controlador.obtenerPorId);
router.post("/", controlador.crear);
export default router;
Middleware tipado
Los middlewares en Express con TypeScript siguen la misma firma tipada:
// Middleware de autenticación
import { Request, Response, NextFunction } from "express";
// Extender el tipo Request para añadir el usuario autenticado
declare global {
namespace Express {
interface Request {
usuario?: { id: string; rol: "admin" | "usuario" };
}
}
}
function autenticar(req: Request, res: Response, next: NextFunction): void {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
res.status(401).json({ mensaje: "Token requerido" });
return;
}
// Verificar token (simplificado)
try {
req.usuario = { id: "usuario-123", rol: "usuario" };
next();
} catch {
res.status(401).json({ mensaje: "Token inválido" });
}
}
// Middleware de validación
function validarCuerpo<T>(esquema: (body: unknown) => body is T) {
return (req: Request, res: Response, next: NextFunction): void => {
if (!esquema(req.body)) {
res.status(400).json({ mensaje: "Datos de entrada inválidos" });
return;
}
next();
};
}
Manejo de errores global
// src/middleware/errores.ts
import { Request, Response, NextFunction } from "express";
interface ErrorHTTP extends Error {
statusCode?: number;
}
export function manejarErrores(
error: ErrorHTTP,
_req: Request,
res: Response,
_next: NextFunction
): void {
const statusCode = error.statusCode ?? 500;
const mensaje = statusCode === 500 ? "Error interno del servidor" : error.message;
console.error(`[Error ${statusCode}] ${error.message}`);
res.status(statusCode).json({
error: {
mensaje,
...(process.env.NODE_ENV === "development" && { pila: error.stack })
}
});
}
Aplicación principal
// src/index.ts
import express from "express";
import usuariosRouter from "./rutas/usuario.rutas";
import { manejarErrores } from "./middleware/errores";
const app = express();
const PUERTO = process.env.PORT ? parseInt(process.env.PORT) : 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api/usuarios", usuariosRouter);
app.use(manejarErrores);
app.listen(PUERTO, () => {
console.log(`Servidor escuchando en http://localhost:${PUERTO}`);
});
export default app;
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Configurar un proyecto Node.js con TypeScript usando las opciones correctas de tsconfig
- Tipar correctamente Request, Response y NextFunction de Express
- Crear interfaces tipadas para el cuerpo de peticiones y parámetros de ruta
- Implementar middleware tipado para autenticación y validación
- Estructurar un proyecto Express en capas (router, controlador, servicio) con TypeScript