NestJS: estructura y convenciones para backends mantenibles

Por Juan Villarroel Publicado el 5 min de lectura

NestJS: estructura y convenciones para backends mantenibles
Nestjs Backend TypeScript Opinión

Respuesta rápida: Una evaluación de NestJS para backends con TypeScript: estructura modular, inyección de dependencias, ventajas, límites y criterios de adopción.

Si eres desarrollador web y has trabajado con Node.js, probablemente ya conozcas Express.js. Es una librería muy popular y flexible, pero cuando los proyectos crecen, puede volverse difícil de manejar. Aquí es donde entra NestJS, un framework progresivo para construir aplicaciones del lado del servidor con TypeScript.

Qué es NestJS

NestJS es un framework para Node.js basado en TypeScript que utiliza arquitectura modular e incorpora conceptos de programación orientada a objetos (OOP), programación funcional (FP) y programación reactiva (RP). Está diseñado para ser escalable y estructurado, lo que lo hace ideal para proyectos grandes.

Cómo funciona

NestJS se basa en los siguientes componentes clave:

Módulos

Son el corazón de NestJS. Permiten organizar la aplicación en partes reutilizables y mantenibles.

import { Module } from "@nestjs/common";
import { UsersModule } from "./users/users.module";

@Module({
  imports: [UsersModule],
})
export class AppModule {}

Controladores

Manejan las solicitudes HTTP y responden con los datos necesarios.

import { Controller, Get } from "@nestjs/common";

@Controller("users")
export class UsersController {
  @Get()
  findAll() {
    return "Lista de usuarios";
  }
}

Servicios

Contienen la lógica de negocio y pueden ser inyectados en los controladores.

import { Injectable } from "@nestjs/common";

@Injectable()
export class UsersService {
  getUsers() {
    return ["Juan", "María", "Carlos"];
  }
}

Decoradores

Son funciones que ayudan a estructurar el código de forma declarativa. Algunos ejemplos son @Controller(), @Get(), @Injectable(), etc.

Ventajas de NestJS

  • Estructura escalable: Organiza el código de forma modular y limpia.
  • TypeScript integrado: Reduce errores y mejora la mantenibilidad.
  • Inyección de dependencias: Facilita la reutilización y modularización del código.
  • Compatible con Express y Fastify: Puedes elegir el motor HTTP.
  • Amplia comunidad y documentación: Muy bien soportado y en constante crecimiento.

Aspectos a considerar

  • Curva de aprendizaje: Si vienes de Express.js, te puede costar adaptarte a la estructura modular.
  • Mayor complejidad inicial: Para proyectos pequeños, NestJS puede sentirse sobrecargado.
  • Uso intensivo de decoradores: No todos los desarrolladores están familiarizados con ellos.

Mi experiencia con NestJS

La primera vez que vi NestJS me pareció intimidante. De entrada, el código base ya trae una estructura bastante robusta, lo que puede hacer que los nuevos usuarios se sientan abrumados. Además, su similitud con Angular (ya que está inspirado en él) me hizo dudar si realmente era lo que necesitaba.

Sin embargo, conforme lo fui utilizando, me di cuenta de que su complejidad inicial se traduce en una gran ventaja a largo plazo. Cada funcionalidad que puedas necesitar, probablemente el equipo de NestJS ya la haya pensado por ti. Desde validaciones con class-validator, integraciones con bases de datos como PostgreSQL y MongoDB, caching, logging, subida de archivos y documentación con OpenAPI, hasta la posibilidad de trabajar con WebSockets y microservicios con Redis, Kafka, RabbitMQ y más. Personalmente, lo he usado con Kafka y la integración es impecable. Además, su CLI es una herramienta poderosa que agiliza la creación de nuevos componentes.

He leído muchas críticas hacia NestJS, especialmente sobre su rendimiento, comparándolo con Express.js. Pero estas comparaciones no tienen sentido: NestJS usa Express.js bajo el capó (o Fastify, si lo prefieres) y le añade funcionalidades que facilitan el desarrollo. Es como construir una aplicación con Express.js y agregar manualmente decenas de paquetes para obtener la misma experiencia que NestJS ya proporciona de serie.

Al final, la decisión de usar NestJS depende de las necesidades y objetivos del producto. Si buscas rapidez y flexibilidad máxima, Express.js es una gran opción. Si necesitas una aplicación mantenida por varias personas y con convenciones claras desde el inicio, NestJS puede ser una buena elección. En mi caso, se ha convertido en una de mis primeras opciones para desarrollo backend.

Conclusión

Si buscas un framework para construir aplicaciones backend escalables, mantenibles y con una estructura bien definida, NestJS es una opción sólida. Su integración con TypeScript, su modularidad y su ecosistema lo convierten en una alternativa madura a Express.js.

Antes de adoptarlo en producción

No elegiría NestJS únicamente porque ofrece muchas integraciones. Primero comprobaría tamaño del equipo, vida esperada del producto, experiencia con TypeScript y necesidades operativas. Una API pequeña puede beneficiarse más de la claridad de Express; un backend mantenido por varias personas suele recuperar antes el coste de aprender las convenciones de Nest.

Haría una prueba con una feature representativa, no con un CRUD artificial. Esa prueba debería incluir autenticación, validación, acceso a datos, un error de negocio y una dependencia externa. Así puedes evaluar cuánto ayudan guards, pipes, providers y módulos en el tipo de problema que realmente tendrás.

También comprobaría estos riesgos:

  • Providers globales que esconden dependencias.
  • Módulos que exportan casi todo y dejan de encapsular.
  • Servicios enormes con decenas de responsabilidades.
  • Decoradores aplicados por costumbre sin una política clara.
  • Tests que recrean el contenedor completo para verificar una función sencilla.

NestJS funciona mejor cuando el equipo acuerda qué pertenece al framework y qué pertenece al dominio. Un guard puede resolver autenticación transversal; una regla como “solo el propietario puede cancelar una factura pendiente” suele ser más clara cerca del caso de uso. Mantener esa separación evita que el negocio termine expresado como una colección de decorators.

Finalmente, mediría tiempo de arranque, memoria, latencia y experiencia de desarrollo con un flujo real. El objetivo no es demostrar que Nest es rápido o lento en abstracto, sino saber si su coste es relevante frente a base de datos, red y productividad del equipo.

Un camino de adopción que evita ceremonia

Comenzaría con un único módulo de negocio y mantendría AppModule como composición. Crearía DTOs para validar el contrato HTTP, pero las reglas importantes vivirían en una operación que pueda probarse sin levantar el servidor. El repositorio sería una frontera solo cuando exista persistencia real que sustituir o integrar.

Después añadiría capacidades transversales en el orden en que el producto las necesita: configuración validada, manejo consistente de errores, logging con contexto, autenticación y observabilidad. No instalaría una cola, CQRS o microservicios en la plantilla inicial para demostrar escalabilidad.

Cada nueva convención debería responder una pregunta concreta: ¿dónde se valida?, ¿cómo se reporta un error?, ¿quién puede ejecutar esta operación?, ¿cómo se prueba? Si el equipo no puede explicar la convención en una revisión de código, el framework no la hará comprensible automáticamente.

Este enfoque deja que NestJS aporte estructura sin convertir su documentación completa en una lista de requisitos. La aplicación crece a partir de necesidades observadas, no de todas las posibilidades que el framework ofrece.

Antes de distribuir módulos como servicios, revisa por qué un monolito con vertical slices suele ser un mejor comienzo. El criterio general es el mismo que desarrollo en código perfecto vs decisiones correctas: adoptar estructura cuando resuelve un problema presente, no para anticipar todos los problemas posibles.

Publicaciones Relacionadas