Guía de migración a NestJS 12: ESM, esquemas estándar y observabilidad
Esta guía explica los cambios principales de NestJS 12: paquetes ESM, validación con esquemas estándar, capacidades de observabilidad integradas y actualizaciones en la CLI, además de cómo realizar una migración segura.
NestJS 12 ya está disponible, y a diferencia de un típico salto de versión importante, esta actualización no gira en torno a una única característica destacada.
En su lugar, aborda varios aspectos del ecosistema de NestJS al mismo tiempo, actualizándolos según cómo es en realidad el desarrollo backend hoy en día.
Entre las actualizaciones más notables se encuentran:
- Los paquetes de Nest ahora se distribuyen en formato ESM
- Validación basada en Standard Schema
- Serialización basada en Standard Schema
- Observabilidad integrada a través de
@nestjs/observe - Una CLI de NestJS reescrita
- Soporte para Rspack en nuevas configuraciones de monorepo
- Vitest y oxlint disponibles por defecto en nuevos proyectos
- Detección más inteligente de rutas conflictivas
- Códigos de error que las herramientas pueden interpretar
- Registro estructurado, adecuado para máquinas
Si ya mantienes un códigobase de NestJS, hay un detalle que debería disipar cualquier preocupación de inmediato:
No es necesario cambiar tu aplicación a ESM solo porque NestJS 12 en sí se distribuye como ESM.
Ese hecho por sí solo convierte lo que podría parecer una actualización disruptiva en algo que puedes adoptar a tu propio ritmo.
NestJS 12 se centra en actualizar el framework
NestJS se utiliza ampliamente para crear servicios backend bien organizados sobre Node.js y TypeScript.
Su estructura general no ha cambiado y sigue siendo reconocible para quienes ya lo han utilizado anteriormente:
NestJS 12 Is About Modernizing the Framework
NestJS has become one of the popular ways to build structured backend applications with Node.js and TypeScript.
Its architecture is familiar:
Lo que sí ha cambiado es el entorno de Node.js circundante.
La adopción de ESM sigue creciendo en todo el ecosistema.
Bibliotecas de esquemas como Zod ganan popularidad.
Compiladores más nuevos y rápidos están reemplazando a las herramientas de compilación antiguas.
La observabilidad se considera cada vez más como un aspecto fundamental durante el desarrollo, y no algo que se añade posteriormente una vez lanzado un servicio.
NestJS 12 está, en esencia, poniéndose al día con todas estas tendencias al mismo tiempo.
No obstante, lo notable es que nada de esto obliga a las aplicaciones existentes a adoptarlo todo desde el primer día.
1. Los paquetes principales de Nest ahora se distribuyen en formato ESM
Tal vez el cambio más evidente en esta versión es que los paquetes principales de Nest ahora se publican en formato ESM.
Si su proyecto está basado en CommonJS, esto podría parecer que exige una reescritura extensa.
Afortunadamente, las versiones modernas de Node.js admiten require(esm).
En la práctica, eso significa que la mayoría de las aplicaciones en CommonJS pueden seguir funcionando tal como están, sin una conversión completa a ESM.
Por ejemplo, esta línea sigue funcionando exactamente como antes:
const { NestFactory } = require('@nestjs/core');
No es necesario reescribirlo de la siguiente manera:
import { NestFactory } from '@nestjs/core';
No obstante, NestJS 12 sí eleva la versión mínima de Node.js requerida.
Específicamente, necesitará una de las siguientes versiones:
Node.js 20.19+
or
Node.js 22.12+
Node.js 21.x no cuenta con soporte explícito.
Por lo tanto, antes de modificar las dependencias de NestJS, confirme qué versión de Node está utilizando:
node --version
Verificar esto temprano en su pipeline CI/CD también es una precaución sensata.
2. Pasar a ESM es una opción, no un requisito
Este punto merece especial énfasis para los equipos que mantienen proyectos existentes.
Aquí tienen lugar dos migraciones separadas.
NestJS mismo está transformando sus paquetes a ESM.
No obstante, su aplicación no está obligada a hacerlo de inmediato.
En otras palabras, esta configuración es perfectamente válida:
Existing CommonJS Application
↓
NestJS 12
↓
Continue running CommonJS
en lugar de verse forzados a:
CommonJS
↓
Rewrite everything
↓
ESM
↓
NestJS 12
Aun así, las herramientas personalizadas para tu proyecto pueden seguir generando complicaciones.
Vale la pena revisar detenidamente:
- scripts personalizados de Bootstrap
- procesos de compilación
- ejecutores de pruebas
- configuración de bundlers
- patrones de importación no estándar
- Herramientas específicas para CommonJS
Incluso si NestJS funciona bien por sí mismo, un script o herramienta en la que confíes en otra parte del proceso podría no funcionar.
3. El soporte para esquemas estándar cambia la forma de validación
Una de las adiciones más destacadas en NestJS 12 es el soporte integrado para esquemas estándar.
Si has estado trabajando con TypeScript recientemente, es probable que hayas encontrado bibliotecas como Zod, Valibot o ArkType. Estas herramientas se encargan de la validación en tiempo de ejecución al mismo tiempo que se integran perfectamente con la verificación de tipos de TypeScript.
Historialmente, NestJS se basó en DTOs basados en clases junto con class-validator. Ese patrón sigue funcionando y no se va a eliminar. NestJS 12 simplemente añade una alternativa.
Así es como se ve en la práctica:
@Post()
create(
@Body({
schema: createUserSchema,
})
body: CreateUserDto,
) {
return this.usersService.create(body);
}
Luego lo conectas a nivel global:
app.useGlobalPipes(
new StandardSchemaValidationPipe(),
);
Con esto en su lugar, el esquema en sí se encarga de validar las solicitudes entrantes. Esto es especialmente útil si tu código ya define esquemas usando Zod u otra biblioteca que siga la especificación Standard Schema.
4. Zod se integra de manera más directa con NestJS
Supongamos que ya tienes un esquema de Zod definido de esta manera:
const createUserSchema = z.object({
name: z.string().min(1),
email: z.email(),
});
En lugar de duplicar esa lógica en una capa de validación específica para NestJS, puedes integrar directamente el esquema existente en el punto de entrada de las solicitudes.
Lo mismo aplica a los parámetros de ruta:
@Get(':id')
findOne(
@Param('id', {
schema: z.coerce
.number()
.int()
.positive(),
})
id: number,
) {
return this.usersService.findOne(id);
}
Esto reduce la lógica repetida. En lugar de mantener un conjunto de reglas de validación para el cliente y otro para el servidor, los equipos pueden compartir un único esquema dondequiera que su configuración lo permita. Como beneficio adicional, estos esquemas también pueden utilizarse para generar documentación OpenAPI.
5. El esquema estándar también se aplica a las respuestas salientes
La validación no se limita a lo que llega desde el exterior; lo que se envía también es importante.
Considere un caso en el que un endpoint devuelve accidentalmente algo como:
{
"id": 1,
"name": "John",
"passwordHash": "..."
}
Técnicamente, el manejador sí devolvió un objeto. Pero ese objeto puede exponer más información de la que el contrato de la API estaba destinado a revelar.
Para abordar esto, NestJS 12 incluye StandardSchemaSerializerInterceptor, que verifica y reorganiza los datos salientes antes de que lleguen al cliente.
Por ejemplo:
@UseInterceptors(
StandardSchemaSerializerInterceptor,
)
@SerializeOptions({
schema: userResponseSchema,
})
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
El resultado es una cobertura de validación en ambos extremos del ciclo de solicitud:
Client
↓
Request
↓
Schema Validation
↓
Application
↓
Schema Serialization
↓
Response
↓
Client
Para los servicios desarrollados principalmente en torno a APIs, esa simetría representa una mejora significativa.
6. Observabilidad integrada mediante @nestjs/observe
NestJS 12 también presenta un paquete dedicado a la observabilidad:
@nestjs/observe
Lo que lo diferencia es que conoce la estructura interna de NestJS. Un agente de monitoreo típico solo vería algo como:
POST /users
200
Por el contrario, las herramientas propias de Nest pueden reconocer constructos de nivel superior como controladores, proveedores, resolvers de GraphQL, consumidores de colas, tareas y microservicios.
La instrumentación abarca varias áreas, incluyendo HTTP, GraphQL, gRPC, microservicios, consumidores de colas y tareas programadas con cron.
El objetivo es tratar la observabilidad como algo integrado en el ciclo de vida de la aplicación, y no como algo que se añade externamente a nivel del servidor HTTP.
7. La observabilidad es algo de lo que debe preocuparse el QA
Desde la perspectiva del QA, esta capa de observabilidad merece atención.
Las pruebas no deberían detenerse en el momento en que una API envía una respuesta:
200 OK
Es útil comprender qué ocurrió realmente mientras se generaba esa respuesta.
Considere una solicitud que fluye a través del sistema de la siguiente manera:
Request
↓
Controller
↓
Service
↓
Database
↓
External API
↓
Response
Si una llamada tarda tres segundos en completarse, un estado 200 por sí solo no explica todo.
Lo que realmente se quiere saber es dónde se gastaron esos tres segundos.
Los posibles culpables incluyen:
- consultas a la base de datos lentas
- retrasos provenientes de una API externa
- tiempo invertido en la lógica de la aplicación
Los datos de observabilidad brindan a los equipos de QA e ingeniería una capa adicional de evidencia con la que trabajar, lo que ayuda a relacionar los fallos en las pruebas con el comportamiento real en producción.
8. La validación de configuraciones pasa a usar esquemas estándar
El manejo de configuraciones es otra área que está siendo actualizada.
Historialmente, muchas aplicaciones NestJS dependían de Joi para esto:
ConfigModule.forRoot({
validationSchema: schema,
});
Con NestJS 12, la validación de configuraciones está pasando a utilizar esquemas estándar en su lugar.
Así es como se aplica en la práctica:
ConfigModule.forRoot({
validationSchema: z.object({
NODE_ENV: z
.enum([
'development',
'production',
'test',
])
.default('development'),
PORT: z.coerce
.number()
.default(3000),
}),
});
Joi no ha sido descartado; los proyectos existentes pueden seguir utilizandolo, pero deberán actualizar a Joi 18 o una versión más reciente y trasladar cualquier configuración específica de la biblioteca a:
validationOptions.libraryOptions
Esto forma parte de un esfuerzo más amplio por establecer una interfaz de esquemas compartida en todo el ecosistema del framework.
9. Ahora se pueden detectar automáticamente las rutas conflictivas
Existe un problema sutil en el diseño de la API que a menudo es difícil de identificar hasta que causa problemas.
Supongamos que defines estos dos controladores:
@Get(':id')
findOne() {}
@Get('me')
getCurrentUser() {}
Dependiendo de cómo se resuelven las rutas y del orden en que se declaran, una solicitud a:
/users/me
puede terminar coincidiendo con el patrón:
/users/:id
en lugar de llegar al controlador dedicado /me como se pretendía.
NestJS 12 añade una función de diagnóstico opcional para detectar este tipo de ambigüedad en las rutas.
Se activa de la siguiente manera:
const app = await NestFactory.create(
AppModule,
{
routeConflictPolicy: {
duplicate: 'error',
shadow: 'warn',
},
routeResolutionStrategy:
'specificity',
},
);
Esto permite a los desarrolladores detectar proactivamente reglas de enrutamiento ambiguas, en lugar de encontrárselas por casualidad a través de una respuesta de API confusa más tarde.
10. Códigos de error que las máquinas pueden interpretar realmente
Otra pequeña adición aquí podría resultar de gran importancia para cualquier aplicación que utilice su API.
Tomemos esta excepción:
throw new BadRequestException(
'Password is too weak',
);
Un desarrollador frontend podría sentirse tentado a comparar directamente con el texto del mensaje:
if (message === 'Password is too weak') {
...
}
Ese enfoque es frágil, ya que la redacción puede cambiar.
Un contrato más estable consiste en utilizar un código de error fijo en su lugar:
throw new BadRequestException(
'Password is too weak',
{
errorCode: 'WEAK_PASSWORD',
},
);
De esta forma, el cliente puede verificarlo contra ese código:
WEAK_PASSWORD
en lugar de depender de la redacción exacta del mensaje.
Esto cobra aún más importancia cuando una API tiene varios consumidores, como:
- un frontend web
- una aplicación móvil
- una API dirigida a socios
- servicios internos
Todos ellos pueden confiar en el mismo identificador de error consistente en lugar de analizar texto legible para humanos.
El registro estructurado recibe una mejora
También hay mejoras en el registro de logs en esta versión.
Ahora puede escribir algo como:
logger.log(
'User created',
{
userId: 1,
email: 'foo@bar.com',
},
);
El argumento del objeto se trata como datos estructurados asociados a esa línea de log en particular, en lugar de simplemente como texto adicional para imprimir.
Cuando está activado el modo de salida JSON, estos datos estructurados aparecen bajo la clave params, o se pueden integrar directamente en la entrada de logs utilizando la opción flattenParams.
Esto es muy importante si sus logs se integran en un sistema de monitoreo o observabilidad. En lugar de emitir cadenas simples que deben analizarse posteriormente, su aplicación puede emitir entradas estructuradas que son buscables y filtrables desde el principio.
Por ejemplo, una entrada de log podría verse así:
{
"message": "User created",
"params": {
"userId": 1,
"email": "foo@bar.com"
}
}
Ese formato es mucho más fácil de consultar que intentar extraer campos de un mensaje de texto plano.
La CLI ha sido reconstruida
Otro cambio importante en esta versión es una reestructuración completa de la CLI.
Su base de código se trasladó a ESM. Su conjunto de pruebas pasó de Jest a Vitest. Se añadió cobertura end-to-end para los comandos de la CLI, y la estructura interna de comandos se refactorizó utilizando objetos de contexto tipados.
Nada de esto afecta necesariamente directamente al código de su aplicación. Pero es una señal de que los esfuerzos de modernización no se limitan al propio entorno de ejecución: las herramientas y el flujo de trabajo del desarrollador también se están actualizando.
nest upgrade simplifica el camino de migración
La CLI reconstruida incluye un nuevo comando:
nest upgrade
Antes de aplicar cualquier cambio, puede inspeccionar qué es lo que se pretende modificar:
Before running it, you can preview the changes:
Esto es valioso porque un aumento de versión importante suele afectar a muchos detalles de configuración pequeños e independientes entre sí. La orden de actualización puede manejar automáticamente los cambios mecánicos como:
- actualizar las versiones del paquete
@nestjs/* - actualizar la configuración de webpack
- sustituir GraphQL Playground por GraphiQL
- ajustar el transporte de suscripciones de GraphQL
- actualizar los paquetes relacionados con NATS
- ajustar el uso de
@nestjs/config - actualizar las dependencias de Jest
- actualizar las dependencias de Joi
Una vez finalizado, muestra un resumen de lo que se modificó automáticamente y de qué aún requiere revisión manual.
Los proyectos recién generados comienzan con valores predeterminados modernos
Crear un proyecto completamente nuevo con NestJS 12 ahora le ofrece un punto de partida diferente.
Las nuevas configuraciones de monorepo usan por defecto Rspack como herramienta de empaquetado. Los proyectos nuevos emplean oxlint en lugar de ESLint. Vitest es ahora el ejecutor de pruebas predeterminado para proyectos basados en ESM. Bun también se acepta como opción de gestor de paquetes, junto con las opciones existentes:
npm
yarn
pnpm
Nada de esto modifica retroactivamente los proyectos existentes; esa distinción es importante. NestJS 12 simplemente establece un estándar más moderno para todo lo que se cree en el futuro, permitiendo que las aplicaciones ya existentes migren a su propio ritmo.
Las configuraciones de GraphQL requieren cierta atención
Si estás ejecutando una aplicación GraphQL, hay tareas de migración que no debes omitir.
GraphiQL reemplaza ahora a GraphQL Playground como el entorno de desarrollo integrado predeterminado. Lo más relevante es que el soporte para:
subscriptions-transport-ws
se ha eliminado por completo. Se espera que pases a utilizar:
graphql-ws
Estos dos protocolos no son compatibles entre sí a nivel de transmisión de datos. Eso significa que cambiar el método de transporte de suscripciones en su backend no es un cambio exclusivo del backend: todo lo que consume esas suscripciones también necesita ser actualizado y probado.
NestJS API
↓
GraphQL Subscription
↓
Web / Mobile Client
Actualizar una dependencia únicamente en el lado del servidor no será suficiente para que todo funcione de principio a fin.
También ha cambiado el soporte para NATS
El framework ahora reemplaza el paquete nats por:
@nats-io/transport-node
Si su aplicación importa directamente el paquete antiguo, necesitará actualizar tanto la dependencia como las instrucciones de importación correspondientes.
También hay cambios en la forma en que se manejan los paquetes: ahora las cargas útiles se serializan como cadenas JSON, y cualquier deserializador personalizado que hayas escrito recibirá el objeto de mensaje completo de NATS en lugar de una carga útil ya analizada. Puedes leer el contenido de la carga útil utilizando:
msg.json()
Si las comunicaciones de mensajería son una parte esencial de tu sistema, este es un aspecto que merece ser mencionado específicamente en tus planes de pruebas de integración y regresión.
17. Ha cambiado el orden de los ganchos del ciclo de vida
Hay otro cambio disruptivo relacionado con los ganchos del ciclo de vida.
En NestJS 12, el orden en que se activan los ganchos del ciclo de vida ahora depende de la posición de un componente dentro de la jerarquía.
Esto es importante si tu aplicación depende de una secuencia específica durante:
- inicialización
- arranque
- cierre
- desmontaje
Supongamos que un servicio activa los recursos en este orden:
Database
Queue
Cache
External API
Y otro servicio asume que uno de esos recursos ya está disponible. Después de la actualización, será necesario confirmar que esa suposición sigue siendo válida.
Este es exactamente el tipo de cambio que no necesariamente se manifestará como un fallo en la compilación. Su proyecto puede compilar sin errores y, aun así, funcionar de manera diferente en tiempo de ejecución.
18. Otros cambios importantes que merecen atención
Varios otros ajustes forman parte de esta versión.
NestJS 12 también afecta:
- la estructura de las respuestas de errores de validación
- cómo se manejan las excepciones gRPC
- la coincidencia de patrones con expresiones regulares en Kafka
- las pasarelas WebSocket de ámbito de solicitud
- las razones que se informan al desconectarse de WebSocket
- ganchos previos a la solicitud para microservicios
- el comportamiento de cierre ordenado en Express
- cómo el adaptador HTTP mapea los errores
La mayoría de los proyectos no utilizarán cada una de estas funcionalidades. Pero allí donde su aplicación dependa de alguna de ellas, vale la pena agregar pruebas de regresión específicas para esa función.
19. ¿En qué debe centrarse el QA después de la actualización?
Se podría decir que esta es la pregunta más importante a responder.
Asegurarse de que una actualización importante del framework se haya realizado sin problemas no debería basarse únicamente en ejecutar:
npm test
En lugar de eso, las pruebas deben dividirse en áreas distintas.
API
Verificar:
- autenticación
- autorización
- validación
- respuestas de error
- coincidencia de rutas
- serialización de respuestas
Configuración
Verificar:
- variables de entorno obligatorias
- valores inválidos
- valores por defecto
- configuración de producción
- configuración de pruebas
GraphQL
Cuando sea relevante:
- consultas
- mutaciones
- suscripciones
- GraphiQL
- compatibilidad del cliente
Microservicios
Cuando sea relevante:
- NATS
- Kafka
- gRPC
- serialización de mensajes
- reintentos
- manejo de excepciones
Observabilidad
Si está activado:
- rastreos HTTP
- rastreos GraphQL
- tareas en segundo plano
- consumidores de colas
- errores
- tareas cron
Cierre
Verificar:
- manejo de SIGTERM
- solicitudes activas
- conexiones a la base de datos
- colas
- trabajadores en segundo plano
El objetivo no es responder a:
"¿Se inicia la aplicación?"
Sino responder a:
"¿La aplicación sigue funcionando correctamente en todos los puntos críticos?"
20. Qué significa esta versión para el trabajo de QA
Hay una tendencia más amplia que merece atención.
Los frameworks se vuelven cada vez más automatizados. La validación se está estandarizando. La capacidad de observabilidad se integra directamente. Los registros pasan a ser estructurados por defecto. Los conflictos de rutas pueden detectarse automáticamente. Las herramientas de pruebas se vuelven cada vez más rápidas.
Nada de esto elimina la necesidad de QA. Solo cambia dónde el QA aporta más valor.
En lugar de preguntar únicamente:
"¿Funciona este endpoint?"
QA debe preguntar cada vez más:
"¿El contrato de la API sigue siendo correcto?" "¿Se pueden observar los fallos?" "¿Se aplican correctamente los permisos?" "¿Los errores son legibles por máquinas?" "¿La serialización es correcta?" "¿La aplicación se recupera como debería?" "¿Esta actualización cambia el comportamiento existente?"
El framework puede automatizar ciertas verificaciones por sí mismo. Pero aún así es necesario decidir qué aspectos merecen ser revisados en primer lugar.
21. Un camino sugerido para la migración a NestJS 12
En lugar de actualizar directamente el entorno de producción, comience verificando su entorno actual:
node --version
Confirme que está utilizando:
Node 20.19+
o bien:
Node 22.12+
A continuación, actualice la CLI:
npm i -g @nestjs/cli@latest
Vea de antemano qué efecto tendrá la migración:
nest upgrade --dry-run
Examine cuidadosamente la salida. Luego aplíquela:
nest upgrade
Después de eso, ejecute:
npm test
junto con sus suites de integración y de extremo a extremo.
Preste especial atención a cualquier funcionalidad basada en:
- GraphQL
- NATS
- validación de configuraciones
- tubos personalizados
- ganchos de ciclo de vida
- Webpack
- Herramientas basadas en CommonJS
Cada una de esas áreas conlleva consideraciones de migración que vale la pena revisar por separado.
Pensamientos finales
NestJS 12 no es simplemente una cuestión de añadir otra función.
Representa un paso hacia la alineación de NestJS con la dirección actual del ecosistema de Node.js y TypeScript.
ESM ahora está integrado en la propia arquitectura de paquetes.
Schema estándar abre el framework a Zod, Valibot, ArkType y otras bibliotecas de validación.
Ese mismo ecosistema de esquemas también puede utilizarse para la serialización.
La observabilidad está ahora vinculada de manera más directa a la propia estructura de aplicaciones de Nest.
La CLI ha sido reconstruida con herramientas más recientes.
Rspack, Vitest, oxlint y Bun se están convirtiendo en parte de lo que representa una configuración moderna de NestJS.
Al mismo tiempo, nada de esto obliga a las aplicaciones existentes a cambiar todo de la noche a la mañana.
Puede seguir utilizando CommonJS si lo desea.
Puede seguir confiando en la validación basada en clases.
No es obligatorio cambiar inmediatamente a Vitest u oxlint.
Esa flexibilidad es, sin duda, el aspecto más práctico de esta versión.
NestJS 12 actualiza el framework sin exigir que todas las aplicaciones existentes se modernicen de inmediato.
Para los desarrolladores, esto significa tener más libertad para elegir su propio ritmo.
Para los ingenieros de QA, esto significa otra importante actualización del framework que debe verificarse, no solo a nivel de código, sino también en las APIs, integraciones, capacidades de observabilidad, configuración y el comportamiento real en producción.
Es precisamente allí donde las actualizaciones de framework se vuelven interesantes.
Aumentar el número de versión en package.json es la parte fácil.
Lo que realmente importa es si la aplicación sigue funcionando como quienes dependen de ella esperan.
Lecturas relacionadas
- Compartir un esquema Zod entre tu frontend React y backend Node — Aprende cómo un único esquema Zod puede validar formularios de React, respuestas de API, cuerpos de solicitudes de Express y variables de entorno, al tiempo que genera tipos correspondientes en TypeScript.