
Cómo funciona la serialización en Nest
La serialización en NestJS es el proceso mediante el cual los objetos JavaScript se transforman en un formato que puede ser transmitido a través de la red, típicamente JSON. Este mecanismo es fundamental para controlar qué datos se envían al cliente y cómo se estructuran las respuestas de la API.
flowchart LR
Handler["Handler @Get"] --> Entity[Entity TypeORM]
Entity --> Interceptor[ClassSerializerInterceptor]
Interceptor --> Reflector["Reflector lee @SerializeOptions"]
Reflector --> Transformer[class-transformer instanceToPlain]
Transformer --> Decoradores{Decoradores}
Decoradores --> Excluir["@Exclude oculta password"]
Decoradores --> Exponer["@Expose grupos admin"]
Decoradores --> Transformar["@Transform formato"]
Excluir --> JSON[JSON respuesta]
Exponer --> JSON
Transformar --> JSON
JSON --> Cliente[Cliente HTTP]
El interceptor ClassSerializerInterceptor se ejecuta sobre el valor devuelto por el handler, lee los metadatos @SerializeOptions con Reflector y delega en class-transformer para aplicar @Exclude, @Expose, @Transform y los grupos definidos. El resultado es un POJO seguro que se serializa a JSON antes de salir por el socket.
NestJS implementa la serialización a través de interceptores que se ejecutan automáticamente antes de enviar la respuesta al cliente. El framework utiliza la librería class-transformer para realizar estas transformaciones, permitiendo un control granular sobre el proceso de serialización.
Interceptor de serialización automática
Por defecto, NestJS aplica serialización automática a todas las respuestas de los controladores. Cuando un método de controlador retorna un objeto, el framework lo convierte automáticamente a JSON:
@Controller('users')
export class UsersController {
@Get()
findAll(): User[] {
// Este array se serializa automáticamente a JSON
return [
{ id: 1, name: 'Juan', email: 'juan@email.com', password: 'secret123' },
{ id: 2, name: 'María', email: 'maria@email.com', password: 'secret456' }
];
}
}
El problema con este enfoque es que todos los campos del objeto se incluyen en la respuesta, incluyendo información sensible como contraseñas.
Control de campos con decoradores
Para controlar qué campos se incluyen en la serialización, NestJS utiliza decoradores de class-transformer. El decorador @Exclude() permite omitir campos específicos:
import { Exclude } from 'class-transformer';
export class User {
id: number;
name: string;
email: string;
@Exclude()
password: string;
@Exclude()
createdAt: Date;
}
También puedes usar @Expose() para incluir únicamente los campos marcados, excluyendo todos los demás por defecto:
import { Expose } from 'class-transformer';
export class UserResponseDto {
@Expose()
id: number;
@Expose()
name: string;
@Expose()
email: string;
// password y otros campos se excluyen automáticamente
}
Transformación de datos durante la serialización
La serialización también permite transformar los datos antes de enviarlos. Puedes modificar valores, cambiar nombres de propiedades o crear campos calculados:
import { Transform, Expose } from 'class-transformer';
export class User {
@Expose()
id: number;
@Expose()
@Transform(({ value }) => value.toUpperCase())
name: string;
@Expose()
email: string;
@Expose()
@Transform(({ obj }) => `${obj.name} <${obj.email}>`)
displayName: string;
@Exclude()
password: string;
}
Serialización condicional
Puedes aplicar serialización condicional basada en diferentes criterios como roles de usuario o contexto de la petición:
import { Expose, Transform } from 'class-transformer';
export class User {
@Expose()
id: number;
@Expose()
name: string;
@Expose()
email: string;
@Expose({ groups: ['admin'] })
password: string;
@Expose({ groups: ['admin', 'owner'] })
createdAt: Date;
}
Para usar grupos en el controlador, específica el grupo en las opciones de serialización:
@Controller('users')
export class UsersController {
@Get()
@SerializeOptions({ groups: ['admin'] })
findAll(): User[] {
return this.usersService.findAll();
}
}
Anidación y relaciones
La serialización maneja automáticamente objetos anidados y relaciones entre entidades. Puedes controlar cómo se serializan las relaciones:
import { Expose, Type } from 'class-transformer';
export class Post {
@Expose()
id: number;
@Expose()
title: string;
@Expose()
@Type(() => User)
author: User;
@Expose()
@Type(() => Comment)
comments: Comment[];
}
export class Comment {
@Expose()
id: number;
@Expose()
content: string;
@Exclude()
authorId: number;
}
Configuración global de serialización
Puedes configurar el comportamiento de serialización a nivel global en el archivo principal de la aplicación:
import { ClassSerializerInterceptor } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Configuración global del interceptor de serialización
app.useGlobalInterceptors(
new ClassSerializerInterceptor(app.get(Reflector), {
strategy: 'excludeAll', // Excluir todo por defecto
excludeExtraneousValues: true, // Excluir valores no definidos
enableImplicitConversion: true // Conversión automática de tipos
})
);
await app.listen(3000);
}
Esta configuración asegura que la serialización se aplique consistentemente en toda la aplicación, proporcionando un control uniforme sobre las respuestas de la API y mejorando la seguridad al evitar la exposición accidental de datos sensibles.
Deserialización entrante con class-transformer
La deserialización es la operación inversa: convertir el JSON entrante (@Body(), @Query()) en una instancia tipada de la clase DTO antes de validarla. El ValidationPipe con transform: true invoca plainToInstance automáticamente:
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true },
}),
);
export class CreateUserDto {
@IsEmail()
email: string;
@IsInt()
@Min(18)
age: number; // llega como "30" en query y se convierte a number
@Type(() => Date)
@IsDate()
birthDate: Date;
}
@Type(() => Date) instruye a class-transformer para que convierta el string ISO a un Date real; sin él la validación con @IsDate() fallaría aunque la cadena sea válida.
Patrón Entity vs DTO de respuesta
En proyectos enterprise no se serializa la entidad directamente: se mapea a un DTO de respuesta que solo contiene los campos públicos. Esto desacopla la API de la estructura de la base de datos y simplifica los cambios de esquema:
@Exclude()
export class UserResponseDto {
@Expose() id: string;
@Expose() email: string;
@Expose() name: string;
@Expose()
@Transform(({ obj }) => obj.role.toUpperCase())
role: string;
static from(entity: User): UserResponseDto {
return plainToInstance(UserResponseDto, entity, {
excludeExtraneousValues: true,
});
}
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<UserResponseDto> {
const user = await this.usersService.findOne(id);
return UserResponseDto.from(user);
}
excludeExtraneousValues: true garantiza que cualquier campo de la entidad sin @Expose quede fuera del DTO. Es la forma más segura de evitar fugas accidentales de columnas como passwordHash, internalNotes o audit_* cuando alguien añade columnas nuevas a la entidad.
Buenas prácticas de serialización en producción
- Habilitar
ClassSerializerInterceptorglobal: evita olvidos en handlers concretos. - Definir un DTO por endpoint cuando la respuesta varíe según el rol o el contexto. Más mantenible que usar
groups. - Probar la respuesta en e2e: con
supertestyexpect(res.body).not.toHaveProperty('password')se cazan regresiones tras cambios en la entidad. - No exponer IDs internos si son secuenciales: usar UUID v7 o convertirlos a slugs en el DTO.
- Versionar el DTO (
v1/UserResponseDto,v2/UserResponseDto) cuando la API tenga clientes externos que no controlas.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Nest
Documentación oficial de Nest
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, Nest 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 Nest
Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender el concepto de serialización en NestJS y su importancia. Aprender a utilizar interceptores para la serialización automática de respuestas. Controlar qué campos se incluyen o excluyen en la serialización mediante decoradores. Aplicar transformaciones y serialización condicional según contexto o roles. Configurar la serialización globalmente y manejar objetos anidados y relaciones entre entidades.