
Pipes existentes en Nest y cómo usarlos
Los pipes en NestJS son clases que implementan la interfaz PipeTransform y se ejecutan antes de que los datos lleguen al controlador. Su función principal es transformar los datos de entrada o validar que cumplan con ciertos criterios antes de procesarlos.
flowchart LR
Request["Request body / param / query"] --> Decorator["@Body, @Param, @Query"]
Decorator --> Pipe["PipeTransform.transform value, metadata"]
Pipe --> Validate{"class-validator OK?"}
Validate -->|No| BadReq["BadRequestException 400 + errors"]
Validate -->|Si| Transform[class-transformer plainToClass]
Transform --> ParseInt["ParseIntPipe / ParseUUIDPipe / ParseEnumPipe"]
ParseInt --> Custom[Pipes custom específicos]
Custom --> Handler[Handler del controller con DTO tipado]
BadReq --> ClientError[Cliente recibe 400 con detalle]
El pipeline de pipes es la frontera entre el mundo unknown de la request HTTP y el mundo tipado del controller. Un ValidationPipe global con whitelist: true y forbidNonWhitelisted: true rechaza propiedades extra y nos da DTOs limpios listos para pasar al Service.
NestJS incluye varios pipes integrados que cubren las necesidades más comunes de validación y transformación. Estos pipes se pueden aplicar a nivel de parámetro, método o controlador, proporcionando flexibilidad en su implementación.
Pipes de validación integrados
ValidationPipe es el pipe más utilizado para validar objetos complejos usando decoradores de class-validator. Este pipe transforma automáticamente los objetos planos en instancias de clase y aplica las reglas de validación definidas:
import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common';
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
class CreateUserDto {
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
@MinLength(6)
password: string;
}
@Controller('users')
export class UsersController {
@Post()
@UsePipes(new ValidationPipe())
create(@Body() createUserDto: CreateUserDto) {
return `Usuario creado: ${createUserDto.name}`;
}
}
ParseIntPipe convierte strings en números enteros y valida que la conversión sea exitosa. Es especialmente útil para parámetros de ruta que deben ser numéricos:
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return `Usuario con ID: ${id}`;
}
}
ParseBoolPipe transforma strings como "true" o "false" en valores booleanos reales:
import { Controller, Get, Query, ParseBoolPipe } from '@nestjs/common';
@Controller('products')
export class ProductsController {
@Get()
findAll(@Query('active', ParseBoolPipe) active: boolean) {
return `Productos activos: ${active}`;
}
}
Pipes de transformación de datos
ParseArrayPipe convierte strings separados por comas en arrays y puede aplicar transformaciones adicionales a cada elemento:
import { Controller, Get, Query, ParseArrayPipe } from '@nestjs/common';
@Controller('products')
export class ProductsController {
@Get()
findByIds(
@Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[]
) {
return `Buscando productos: ${ids.join(', ')}`;
}
}
ParseUUIDPipe valida que un string tenga formato UUID válido y puede especificar la versión requerida:
import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common';
@Controller('orders')
export class OrdersController {
@Get(':id')
findOne(@Param('id', new ParseUUIDPipe({ version: '4' })) id: string) {
return `Pedido con UUID: ${id}`;
}
}
ParseEnumPipe valida que un valor pertenezca a un enum específico:
import { Controller, Get, Query, ParseEnumPipe } from '@nestjs/common';
enum OrderStatus {
PENDING = 'pending',
COMPLETED = 'completed',
CANCELLED = 'cancelled'
}
@Controller('orders')
export class OrdersController {
@Get()
findByStatus(
@Query('status', new ParseEnumPipe(OrderStatus))
status: OrderStatus
) {
return `Pedidos con estado: ${status}`;
}
}
Aplicación de pipes a diferentes niveles
Los pipes se pueden aplicar de múltiples formas según el alcance deseado. A nivel de parámetro, el pipe solo afecta a ese parámetro específico:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return `Usuario: ${id}`;
}
A nivel de método, el pipe se aplica a todos los parámetros del método:
@Get(':id')
@UsePipes(ValidationPipe)
findOne(@Param('id') id: string, @Body() updateDto: UpdateUserDto) {
return 'Método con pipe aplicado';
}
A nivel de controlador, el pipe afecta a todos los métodos del controlador:
@Controller('users')
@UsePipes(new ValidationPipe({ whitelist: true }))
export class UsersController {
// Todos los métodos heredan el ValidationPipe
}
Configuración avanzada de pipes
Los pipes integrados aceptan opciones de configuración que modifican su comportamiento. El ValidationPipe, por ejemplo, puede configurarse para eliminar propiedades no definidas en el DTO:
@Post()
@UsePipes(new ValidationPipe({
whitelist: true, // Elimina propiedades no definidas en el DTO
forbidNonWhitelisted: true, // Lanza error si hay propiedades extra
transform: true // Transforma tipos automáticamente
}))
create(@Body() createUserDto: CreateUserDto) {
return 'Usuario creado con validación estricta';
}
Para manejo de errores personalizado, los pipes pueden configurarse con mensajes específicos:
@Get(':id')
findOne(
@Param('id', new ParseIntPipe({
errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE
}))
id: number
) {
return `Usuario: ${id}`;
}
Los pipes también pueden combinarse para aplicar múltiples transformaciones y validaciones en secuencia:
@Get(':id')
findOne(
@Param('id', ParseIntPipe, new DefaultValuePipe(1))
id: number
) {
return `Usuario: ${id}`;
}
Esta flexibilidad permite crear flujos de validación robustos que garantizan que los datos lleguen al controlador en el formate correcto y cumpliendo todas las reglas de negocio establecidas.
Pipes personalizados
Cuando los pipes integrados no cubren un caso, se crea uno propio implementando PipeTransform:
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class TrimPipe implements PipeTransform<string, string> {
transform(value: string): string {
if (typeof value !== 'string') {
throw new BadRequestException('Se esperaba un string');
}
return value.trim().replace(/\s+/g, ' ');
}
}
@Get(':slug')
findBySlug(@Param('slug', TrimPipe) slug: string) {
return this.service.findBySlug(slug);
}
Pipes con dependencias inyectables permiten validar contra base de datos, por ejemplo verificar que un email no esté ya registrado:
@Injectable()
export class UniqueEmailPipe implements PipeTransform<CreateUserDto, Promise<CreateUserDto>> {
constructor(@InjectRepository(User) private readonly repo: Repository<User>) {}
async transform(value: CreateUserDto): Promise<CreateUserDto> {
const exists = await this.repo.exists({ where: { email: value.email } });
if (exists) throw new ConflictException('Email ya registrado');
return value;
}
}
Configuración global recomendada para producción
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
stopAtFirstError: false,
exceptionFactory: (errors) => {
const detail = errors.flatMap((e) =>
Object.values(e.constraints ?? {}),
);
return new BadRequestException({
type: 'https://api.example.com/errors/validation',
title: 'Datos de entrada inválidos',
status: 400,
detail,
});
},
}),
);
await app.listen(3000);
}
whitelist + forbidNonWhitelistedevita inyecciones tipo "envío campos extra que el DTO no declara".transform: trueconvierte el body plano en una instancia real del DTO, necesario para que TypeORM valide los tipos antes de persistir.enableImplicitConversion: truepermite que strings como"30"se conviertan anumbercuando el campo es@IsInt().exceptionFactoryda control total sobre el formato del error 400 (Problem Details RFC 7807, mensajes localizados, códigos de negocio).
Decoradores avanzados de class-validator
class-validator incluye decoradores potentes que cubren casos reales:
import {
IsString, IsEmail, MinLength, MaxLength, Matches,
IsArray, ArrayMinSize, ArrayMaxSize, ValidateNested,
IsDate, IsOptional, IsIn, IsObject, IsPhoneNumber,
} from 'class-validator';
import { Type } from 'class-transformer';
export class AddressDto {
@IsString() @IsNotEmpty() street: string;
@IsString() city: string;
@Matches(/^[0-9]{5}$/) postalCode: string;
}
export class CreateUserDto {
@IsEmail() email: string;
@IsString()
@MinLength(8)
@MaxLength(72)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, {
message: 'La contraseña requiere mayúscula, minúscula y dígito',
})
password: string;
@IsIn(['user', 'admin', 'manager'])
role: string;
@IsPhoneNumber('ES')
@IsOptional()
phone?: string;
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@ValidateNested({ each: true })
@Type(() => AddressDto)
addresses: AddressDto[];
}
@ValidateNested con @Type activa la validación recursiva sobre objetos anidados. Sin @Type el class-transformer no sabría qué clase usar y los decoradores hijos quedarían inactivos.
Validación en tests
Para tests unitarios del DTO sin levantar la app:
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
it('rechaza email mal formado', async () => {
const dto = plainToInstance(CreateUserDto, {
email: 'no-es-email',
password: 'Aa1aaaaaaa',
role: 'user',
addresses: [{ street: 'X', city: 'Madrid', postalCode: '28001' }],
});
const errors = await validate(dto);
expect(errors).toHaveLength(1);
expect(errors[0].property).toBe('email');
});
Esto permite validar reglas complejas con cobertura quirúrgica antes de probar el endpoint completo en e2e.
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 qué son los pipes en NestJS y su función principal. Aprender a utilizar los pipes integrados para validación y transformación de datos. Aplicar pipes a diferentes niveles: parámetro, método y controlador. Configurar opciones avanzadas de los pipes para personalizar su comportamiento. Combinar múltiples pipes para crear flujos de validación robustos.