NestJS vs Express: cuándo elegir cada uno en 2026
3 julio 2026 · 9 min de lectura
La pregunta sigue apareciendo en cada proyecto: ¿NestJS o Express? En 2026 la respuesta no es "depende" por pereza — es "depende" porque los dos marcos resuelven problemas distintos. Express es una librería HTTP minimalista. NestJS es un framework opinionated con arquitectura de clases, inyección de dependencias y un ecosistema de módulos. Elegir mal cuesta caro: refactorizar un backend Express monolito a algo mantenible es trabajo de semanas.
Esta comparativa tiene código real, benchmarks medidos y una matriz de decisión. Guárdala como referencia. Si quieres más contexto sobre nuestra stack, mira apps aisladas vs sistemas.
Filosofía: minimalismo vs convención
Express te da app.get(), app.use() y poco más. Tú decides la estructura: dónde van los controladores, cómo se valida, cómo se inyecta. Es libertad absoluta — y el origen de la mayoría de backends inmantenibles que llegan a nuestras manos. Cada equipo estructura Express a su manera, y cuando crece, crece en caos.
NestJS impone arquitectura: controladores, servicios, módulos, pipes, guards, interceptors. Si vienes de Spring o Angular, es familiar. La curva inicial es mayor, pero la convención escala. Un proyecto NestJS de 50 módulos es navegable. Un proyecto Express de 50 archivos sueltos no lo es.
El mismo endpoint en los dos
Para que se vea la diferencia, aquí está un endpoint POST /users con validación en cada uno.
Express 5:
import express from 'express';
const app = express();
app.use(express.json());
app.post('/users', async (req, res) => {
const { email, name } = req.body;
if (!email || !email.includes('@'))
return res.status(400).json({ error: 'email inválido' });
if (!name || name.length < 2)
return res.status(400).json({ error: 'name requerido' });
const user = await db.user.create({ email, name });
res.status(201).json(user);
});
app.listen(3000);
Funciona. Pero la validación es manual, no hay tipado en req.body, no hay separación de capas. Cuando añades 20 endpoints más, esto se convierte en una bola de lodo.
NestJS 11:
import { Body, Controller, Post } from '@nestjs/common';
import { IsEmail, MinLength } from 'class-validator';
class CreateUserDto {
@IsEmail() email: string;
@MinLength(2) name: string;
}
@Controller('users')
export class UsersController {
constructor(private readonly users: UsersService) {}
@Post()
create(@Body() dto: CreateUserDto) {
return this.users.create(dto);
}
}
La validación la hace class-validator vía ValidationPipe global. El DTO define el contrato. El controlador solo delega al servicio. Si eliminas el controlador, el servicio sigue siendo testeable. Esa separación es lo que mantiene un proyecto navegable con 200 endpoints.
Rendimiento: benchmarks reales
Mitad 2026, medimos con autocannon -c 100 -d 30 en un Ubuntu 24.04 con Node 22 LTS. Hello-world JSON en cada framework:
- Express 5: ~38.000 req/s, P99 latencia 3.2ms.
- NestJS 11 (Express adapter): ~34.000 req/s, P99 4.1ms.
- NestJS 11 (Fastify adapter): ~52.000 req/s, P99 2.8ms.
- Fastify 5 (bare): ~63.000 req/s, P99 2.1ms.
El overhead de NestJS sobre Express es ~10% en hello-world. Con Fastify adapter, NestJS supera a Express bare. En endpoints reales con BD, el overhead del framework desaparece dentro del ruido de la latencia de base de datos. Si eliges Express por 4.000 req/s en un hello-world, estás optimizando lo equivocado.
El cuello de botella de un backend en producción casi nunca es el framework. Es la base de datos, las llamadas externas y las colas. NestJS no te frena. Te organiza.
Inyección de dependencias
NestJS trae un DI container nativo. Registras un provider en un módulo y lo inyectas en cualquier servicio del mismo módulo:
@Module({
providers: [UsersService, PrismaService],
controllers: [UsersController],
})
export class UsersModule {}
En Express tienes que montar tu propio DI: typedi, awilix, o pasar dependencias a mano. Funciona, pero es solución casera. NestJS lo resuelve de fábrica, con scoping por módulo y testing trivial — mock del provider y ya.
Testing
NestJS brilla aquí. El testing module mockea dependencias sin fricción:
const moduleRef = await Test.createTestingModule({
controllers: [UsersController],
providers: [
UsersService,
{ provide: PrismaService, useValue: mockPrisma },
],
}).compile();
const ctrl = moduleRef.get(UsersController);
expect(await ctrl.create({ email: 'a@b.c', name: 'Ana' }))
.toEqual({ id: 1, email: 'a@b.c', name: 'Ana' });
En Express, supertest + mock manual. Funciona, pero requiere disciplina. El problema no es que no se pueda testear Express — es que nada te obliga a hacerlo bien.
Matriz de decisión
Esto es lo que ponemos encima de la mesa cuando un cliente nos pregunta qué stack usar:
- Usa Express si: es un prototipo de fin de semana, un webhook simple, una API de menos de 10 endpoints sin lógica de negocio, o ya tienes una base Express y migrar no compensa.
- Usa NestJS si: el backend va a crecer más de 15 endpoints, hay lógica de negocio con múltiples servicios, necesitas colas (BullMQ), websockets, o vas a meter agentes de IA que requieren orquestación.
- Usa NestJS + Fastify si: necesitas máximo rendimiento y ya pagaste la curva de aprendizaje de NestJS. El adapter es una línea.
- No uses Ninguno si: necesitas real-time puro con miles de conexiones — usa Bun + Hono o directamente Go/Rust. Node no es siempre la respuesta.
El coste real de elegir mal
Hemos heredado proyectos Express de 8.000 líneas sin un solo test, sin separación de capas, con validación a mano en cada ruta. La refactorización a una arquitectura modular tardó tres semanas. Si hubiera empezado en NestJS, la refactorización no habría sido necesaria — la convención habría mantenido el orden desde el día uno.
El coste de Express no está en el día 1. Está en el día 400, cuando el equipo ha crecido, hay cinco personas tocando el mismo archivo y nadie sabe dónde está la lógica de validación de ese endpoint que falla en producción a las 2am.
Por eso en SOM-OS nuestra stack por defecto es NestJS 11 + PostgreSQL + Redis + BullMQ + LangChain. No porque Express sea malo — porque los sistemas que construimos necesitan la arquitectura que NestJS impone. Si quieres entender por qué esa arquitectura importa antes de escribir una línea de código, lee sobre el Mapa del Caos y nuestro enfoque de apps aisladas vs sistemas.
Conclusión
Express es una herramienta excelente para lo que fue diseñada: HTTP minimalista. En 2026 sigue siendo válido para proyectos pequeños y prototipos. NestJS no lo reemplaza — lo complementa. Cuando un proyecto crece, NestJS te ahorra el refactor que Express te obligará a hacer tarde o temprano.
La regla que usamos: si el proyecto vive más de 6 meses o tiene más de 2 desarrolladores, NestJS. Si es un experimento que se borra el lunes, Express y a mover. La diferencia no es de rendimiento — es de mantenibilidad. Y la mantenibilidad es lo que cuesta dinero real, no los 4.000 req/s que pierdes en hello-world.
El framework no hace el código bueno. Pero un framework con convención hace más difícil escribir código malo. Eso, a largo plazo, vale más que cualquier benchmark.