乐闻世界logo
搜索文章和话题

How do NestJS pipes and validation work?

2月17日 22:31

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:

  1. Transformation: Convert input data to the required format
  2. Validation: Validate input data and throw an exception if validation fails

Basic Pipe Structure

typescript
import { 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

typescript
import { 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:

typescript
import { 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.

typescript
import { 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

typescript
import { 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

typescript
import { 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

typescript
import { 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

bash
npm install class-validator class-transformer

Create DTO Class

typescript
import { 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

typescript
import { 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

typescript
import { 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

  1. Use DTOs: Create data transfer objects for all request bodies
  2. Enable Global Validation: Enable ValidationPipe at the application level
  3. Use Whitelist: Enable whitelist and forbidNonWhitelisted options
  4. Custom Error Messages: Provide meaningful error messages for validation rules
  5. Combine Validators: Use multiple validators to create complex validation rules
  6. Test Validation: Write tests for DTOs and pipes
  7. Document: Add clear documentation comments to DTO classes
  8. 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.

标签:NestJS