La pregunta parece simple: ¿Express o NestJS? Pero debajo de esa pregunta hay otra más importante, que es la que realmente determina la respuesta: ¿qué tipo de backend necesitas mantener en dos años?
La infografía que circula en la comunidad Node.js lo resume bien: no se trata de cuál es mejor. Se trata de cuál vas a necesitar mantener después. Y en Ab4cus, después de construir decenas de backends para sistemas fintech y bancarios en LATAM, tenemos una posición clara con los argumentos técnicos para defenderla.
Express.js: el framework que no se interpone en tu camino
Express es minimalista por diseño. No te da estructura — te da herramientas. Tú decides cómo organizar el código, cómo manejar errores, cómo implementar autenticación, cómo separar responsabilidades. Esa flexibilidad es su mayor fortaleza y su mayor riesgo.
Express gana cuando:
- El backend necesita ser simple y directo. Un webhook de notificaciones, una API de 5 endpoints, un microservicio de un solo propósito, Express es la herramienta más rápida para llegar al primer deploy funcional.
- Prototipado rápido. Cuando la prioridad es validar si la API funciona antes de preocuparse por cómo escala, Express te quita el camino. Sin decoradores, sin módulos, sin DI solo rutas y middleware.
- El equipo es pequeño y el scope está bien definido. Un desarrollador con Express puede tener un servicio interno corriendo en horas.
- Tienes una arquitectura de microservicios con servicios muy específicos. Cada microservicio hace una cosa y la hace bien en ese contexto, la estructura de NestJS puede ser overhead innecesario.
Lo que Express no te da: convenciones. Y eso es un problema cuando el proyecto crece. Dos desarrolladores de Express escriben el mismo backend de formas completamente distintas. El tercero que entra al equipo tiene que aprender no solo la lógica de negocio sino también las decisiones arquitectónicas que tomaron los anteriores — que raramente están documentadas.
NestJS: el framework que te da estructura antes de que la necesites
NestJS es opinionated. Tiene una forma de hacer las cosas módulos, controladores, servicios, guards, pipes, interceptors y esa forma no es negociable. Para un desarrollador acostumbrado a la libertad de Express, la primera semana con NestJS puede sentirse restrictiva.
La segunda semana, empieza a entender por qué.
NestJS gana cuando:
- El backend está destinado a crecer. La arquitectura modular de NestJS escala naturalmente: cada dominio de negocio es un módulo con su controlador, su servicio y sus dependencias declaradas explícitamente. Agregar un nuevo módulo de compliance, pagos o notificaciones no requiere entender el código existente solo seguir el patrón.
- El equipo va a rotar o crecer. Un desarrollador nuevo en un proyecto NestJS puede ser productivo en días porque la estructura es predecible. En Express, depende de cuánta disciplina tuvo el equipo anterior.
- Necesitas auth, validación, base de datos y testing integrados. NestJS tiene opiniones sobre todo eso y esas opiniones son generalmente correctas. Passport para auth, class-validator o Zod para validación, TypeORM/Prisma para DB, Jest para testing todo encaja.
- El código tiene que ser auditable. En sistemas financieros regulados, la trazabilidad del código importa. Los interceptors de NestJS para logging, los guards para autorización y los pipes para validación tienen lugares definidos un auditor técnico puede encontrar cada componente de seguridad sin buscar.
La diferencia técnica que más impacta: la inyección de dependencias
NestJS implementa Dependency Injection (DI) como ciudadano de primera clase. Cada servicio declara sus dependencias en el constructor, y el contenedor de IoC de NestJS las resuelve automáticamente.
En el stack de Ab4cus, esto tiene una consecuencia práctica enorme para el testing:
// NestJS — el KYCService recibe sus dependencias
@Injectable()
export class KYCService {
constructor(
private readonly prisma: PrismaService,
@InjectQueue('kyc-processing') private readonly kycQueue: Queue,
private readonly logger: PinoLogger,
) {}
async submitKYC(data: KYCSubmission): Promise<KYCResult> {
const validated = kycSchema.parse(data);
const kyc = await this.prisma.kYC.create({ data: validated });
await this.kycQueue.add('process-kyc', { kycId: kyc.id });
return kyc;
}
}
// Test — reemplazas las dependencias con mocks trivialmente
const module = await Test.createTestingModule({
providers: [
KYCService,
{ provide: PrismaService, useValue: mockPrisma },
{ provide: getQueueToken('kyc-processing'), useValue: mockQueue },
],
}).compile();
En Express puro, testear una función que usa Prisma, una cola y un logger requiere mocking manual de módulos — más frágil, más verboso, más propenso a falsos positivos.
Guards, Pipes e Interceptors: el middleware con propósito declarado
En Express, el middleware es una función que hace cualquier cosa. En NestJS, hay tres tipos con responsabilidades específicas:
Guards — deciden si una request puede continuar. Autenticación JWT, verificación de roles, rate limiting por usuario. En el sistema de compliance de BAS, el guard de autorización verifica que el analista tiene acceso al expediente específico antes de que la request llegue al controlador:
@UseGuards(JwtAuthGuard, ComplianceRoleGuard)
@Get('cases/:id')
async getCase(@Param('id') id: string) {
return this.complianceService.getCase(id);
}
Pipes — transforman y validan los datos de entrada. Nunca llega un campo inválido al servicio de negocio porque el pipe lo rechaza antes. La transferencia con monto negativo, el UUID malformado, el RUT sin formato — son errores de validación que el pipe captura con un 400 automático:
@Post('transfer')
@UsePipes(new ZodValidationPipe(transferSchema))
async createTransfer(@Body() dto: CreateTransferDto) {
// Si llegamos aquí, dto es válido garantizado
return this.transactionService.createTransfer(dto);
}
Interceptors — transforman la respuesta o agregan comportamiento transversal. El logging de cada transacción financiera — quién hizo qué, cuánto tardó, qué respondió — vive en un interceptor que se aplica globalmente sin que ningún controlador tenga que recordar hacerlo:
@Injectable()
export class AuditInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const start = Date.now();
return next.handle().pipe(
tap(() => {
this.logger.info({
userId: request.user?.id,
path: request.url,
duration: Date.now() - start,
}, 'Request completed');
})
);
}
}
En Express, ese mismo logging requiere que cada route handler lo implemente — o que confíes en que el desarrollador que escribió el endpoint no lo olvidó.
La consistencia del equipo: el argumento más subestimado
La infografía lo pone en el último punto de NestJS y es el más importante para proyectos de empresa: more team consistency.
Cuando el equipo de Ab4cus incorpora un desarrollador nuevo a un proyecto NestJS, el onboarding técnico tiene una estructura predecible:
- La estructura de módulos refleja los dominios del negocio — KYC, Compliance, Transactions, Notifications
- Cada módulo tiene exactamente los mismos archivos:
*.module.ts,*.controller.ts,*.service.ts,*.spec.ts - La autenticación siempre está en los guards, la validación siempre en los pipes, el logging siempre en los interceptors
- Las dependencias están declaradas explícitamente — no hay globals ocultos ni singletons que alguien olvidó documentar
Con Express, el onboarding depende de qué tan disciplinado fue el
equipo anterior. Puede ser excelente. Puede ser un laberinto de require circular, middleware en el orden equivocado y lógica de negocio en las rutas.
Por qué Ab4cus usa NestJS como API Gateway
En nuestra arquitectura, NestJS actúa como el API Gateway central: recibe todas las requests del frontend y del móvil, aplica autenticación y autorización, valida los datos, y enruta las operaciones al servicio correcto — ya sea procesamiento en Quarkus (compliance, AML, transacciones de alta carga) o lógica de usuario directo en el servicio NestJS.
La decisión no fue por descarte de Express. Fue por tres razones concretas:
1. tRPC funciona mejor con NestJS. El contexto de tRPC se integra limpiamente con el sistema de módulos de NestJS — los servicios inyectados en el contexto de tRPC son los mismos servicios NestJS, sin duplicación de lógica.
2. Los requerimientos de auditoría regulatoria requieren estructura. Un regulador que inspecciona un sistema KYC necesita ver dónde está la lógica de autorización, cómo se validan los datos de entrada, cómo se registra cada acción. NestJS tiene respuestas arquitectónicas claras a esas preguntas. Express requiere que el equipo las haya tomado y documentado.
3. El equipo crece. En Ab4cus desarrollamos múltiples proyectos en paralelo. Un desarrollador que termina un proyecto y empieza otro encuentra la misma arquitectura NestJS. El conocimiento transfiere — los patrones, las convenciones, incluso los tests.
La respuesta honesta para tu proyecto
Si estás construyendo un webhook sencillo, un script de integración o un servicio interno de un solo propósito que no va a crecer Express es la decisión correcta. Más rápido de arrancar, menos overhead, suficiente para el problema.
Si estás construyendo el backend que va a manejar los datos de tus clientes, procesar sus transacciones, cumplir con reguladores y ser mantenido por un equipo que va a rotar — NestJS es la inversión correcta. La estructura tiene un costo inicial y un retorno compuesto durante años.
La pregunta que resume todo: ¿qué vas a necesitar mantener después?
Si quieres un backend que escale con tu negocio desde el primer día, empieza por calcular el alcance:
→ Estimar el desarrollo de tu backend con Ab4cus
También te puede interesar:
0 Comentarios