Pipe Concept
Pipes in NestJS are classes used for data transformation and validation. They are decorated with @Injectable() and implement the PipeTransform interface. Pipes have two main purposes:
- Transformation: Convert input data to the required format
- Validation: Validate input data and throw an exception if validation fails
Basic Pipe Structure
typescriptimport { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { if (!value) { throw new BadRequestException('Validation failed'); } return value; } }
Using Pipes
Use Pipes at Parameter Level
typescript@Post() create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
Use Pipes at Method Level
typescript@Post() @UsePipes(new ValidationPipe()) create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
Use Pipes at Controller Level
typescript@Controller('cats') @UsePipes(new ValidationPipe()) export class CatsController {}
Global Pipes
typescriptimport { ValidationPipe } from './common/pipes/validation.pipe'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap();
Or use in module:
typescriptimport { Module } from '@nestjs/common'; import { APP_PIPE } from '@nestjs/core'; import { ValidationPipe } from './common/pipes/validation.pipe'; @Module({ providers: [ { provide: APP_PIPE, useClass: ValidationPipe, }, ], }) export class AppModule {}
Built-in Pipes
1. ValidationPipe
Built-in validation pipe provided by NestJS, usually used with class-validator and class-transformer.
typescriptimport { IsString, IsInt, Min } from 'class-validator'; export class CreateCatDto { @IsString() name: string; @IsInt() @Min(0) age: number; @IsString() breed: string; } // Use ValidationPipe @Post() async create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto); }
2. ParseIntPipe
Converts a string to an integer.
typescript@Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); }
3. ParseBoolPipe
Converts a string to a boolean.
typescript@Get(':isActive') findAll(@Param('isActive', ParseBoolPipe) isActive: boolean) { return this.catsService.findAll(isActive); }
4. ParseArrayPipe
Converts a string to an array.
typescript@Get() findAll(@Query('ids', new ParseArrayPipe({ items: String, separator: ',' })) ids: string[]) { return this.catsService.findByIds(ids); }
5. ParseUUIDPipe
Validates and parses a UUID.
typescript@Get(':id') findOne(@Param('id', ParseUUIDPipe) id: string) { return this.catsService.findOne(id); }
6. ParseFloatPipe
Converts a string to a floating point number.
typescript@Get(':price') findByPrice(@Param('price', ParseFloatPipe) price: number) { return this.catsService.findByPrice(price); }
7. DefaultValuePipe
Sets a default value for a parameter.
typescript@Get() findAll(@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number) { return this.catsService.findAll(page); }
Custom Pipes
Validation Pipe Example
typescriptimport { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed'); } return val; } }
Transformation Pipe Example
typescriptimport { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ToLowerCasePipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { if (typeof value === 'string') { return value.toLowerCase(); } return value; } }
Optionated Pipe Example
typescriptimport { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; export interface ParseIntOptions { exceptionFactory?: (error: string) => any; } @Injectable() export class ParseIntPipe implements PipeTransform<string, number> { constructor(private readonly options?: ParseIntOptions) {} transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val)) { const error = `Validation failed. "${value}" is not an integer.`; throw this.options?.exceptionFactory ? this.options.exceptionFactory(error) : new BadRequestException(error); } return val; } } // Use optionated pipe @Get(':id') findOne(@Param('id', new ParseIntPipe({ exceptionFactory: (error) => new BadRequestException(error) })) id: number) { return this.catsService.findOne(id); }
Using class-validator for Validation
Install Dependencies
bashnpm install class-validator class-transformer
Create DTO Class
typescriptimport { IsString, IsInt, IsEmail, Min, Max, IsOptional } from 'class-validator'; export class CreateUserDto { @IsString() @MinLength(3) @MaxLength(20) username: string; @IsEmail() email: string; @IsString() @MinLength(6) password: string; @IsInt() @Min(18) @Max(120) @IsOptional() age?: number; }
Enable Global Validation
typescriptimport { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe({ whitelist: true, // Automatically remove undefined properties forbidNonWhitelisted: true, // Throw error if undefined properties exist transform: true, // Automatically transform types transformOptions: { enableImplicitConversion: true, }, })); await app.listen(3000); } bootstrap();
Common Validation Decorators
String Validation
@IsString()- Must be a string@MinLength(min)- Minimum length@MaxLength(max)- Maximum length@Matches(pattern)- Match regular expression@IsEmail()- Must be a valid email@IsUrl()- Must be a valid URL@IsUUID()- Must be a valid UUID
Number Validation
@IsInt()- Must be an integer@IsFloat()- Must be a floating point number@IsPositive()- Must be positive@IsNegative()- Must be negative@Min(value)- Minimum value@Max(value)- Maximum value
Date Validation
@IsDate()- Must be a date object@MinDate(date)- Not earlier than specified date@MaxDate(date)- Not later than specified date
Other Validation
@IsBoolean()- Must be a boolean@IsArray()- Must be an array@IsEnum(enum)- Must be an enum value@IsOptional()- Optional field@IsDefined()- Must be defined (cannot be undefined or null)
Custom Validators
typescriptimport { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; export function IsCustomProperty(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ name: 'isCustomProperty', target: object.constructor, propertyName: propertyName, options: validationOptions, validator: { validate(value: any, args: ValidationArguments) { // Custom validation logic return value === 'valid'; }, defaultMessage(args: ValidationArguments) { return `${args.property} must be valid`; }, }, }); }; } // Use custom validator export class CreateUserDto { @IsCustomProperty() customField: string; }
Best Practices
- Use DTOs: Create data transfer objects for all request bodies
- Enable Global Validation: Enable ValidationPipe at the application level
- Use Whitelist: Enable
whitelistandforbidNonWhitelistedoptions - Custom Error Messages: Provide meaningful error messages for validation rules
- Combine Validators: Use multiple validators to create complex validation rules
- Test Validation: Write tests for DTOs and pipes
- Document: Add clear documentation comments to DTO classes
- Type Conversion: Enable automatic type conversion to simplify code
Summary
The NestJS pipe and validation system provides:
- Powerful data validation capabilities
- Flexible data transformation mechanisms
- Rich built-in pipes
- Easy-to-extend custom pipes
- Seamless integration with class-validator
Mastering pipes and validation is an important part of building robust, secure NestJS applications. By properly using pipes and validation, you can ensure data integrity and security, reduce runtime errors, and improve application reliability.