管道(Pipe)的概念
管道是 NestJS 中用于数据转换和验证的类。它们使用 @Injectable() 装饰器装饰,并实现 PipeTransform 接口。管道主要有两个用途:
- 转换:将输入数据转换为所需的格式
- 验证:验证输入数据,如果验证失败则抛出异常
管道的基本结构
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; } }
使用管道
在参数级别使用管道
typescript@Post() create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
在方法级别使用管道
typescript@Post() @UsePipes(new ValidationPipe()) create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
在控制器级别使用管道
typescript@Controller('cats') @UsePipes(new ValidationPipe()) export class CatsController {}
全局管道
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();
或者在模块中使用:
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 {}
内置管道
1. ValidationPipe
NestJS 提供的内置验证管道,通常与 class-validator 和 class-transformer 一起使用。
typescriptimport { IsString, IsInt, Min } from 'class-validator'; export class CreateCatDto { @IsString() name: string; @IsInt() @Min(0) age: number; @IsString() breed: string; } // 使用 ValidationPipe @Post() async create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto); }
2. ParseIntPipe
将字符串转换为整数。
typescript@Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); }
3. ParseBoolPipe
将字符串转换为布尔值。
typescript@Get(':isActive') findAll(@Param('isActive', ParseBoolPipe) isActive: boolean) { return this.catsService.findAll(isActive); }
4. ParseArrayPipe
将字符串转换为数组。
typescript@Get() findAll(@Query('ids', new ParseArrayPipe({ items: String, separator: ',' })) ids: string[]) { return this.catsService.findByIds(ids); }
5. ParseUUIDPipe
验证并解析 UUID。
typescript@Get(':id') findOne(@Param('id', ParseUUIDPipe) id: string) { return this.catsService.findOne(id); }
6. ParseFloatPipe
将字符串转换为浮点数。
typescript@Get(':price') findByPrice(@Param('price', ParseFloatPipe) price: number) { return this.catsService.findByPrice(price); }
7. DefaultValuePipe
设置参数的默认值。
typescript@Get() findAll(@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number) { return this.catsService.findAll(page); }
自定义管道
验证管道示例
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; } }
转换管道示例
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; } }
选项化管道示例
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; } } // 使用选项化管道 @Get(':id') findOne(@Param('id', new ParseIntPipe({ exceptionFactory: (error) => new BadRequestException(error) })) id: number) { return this.catsService.findOne(id); }
使用 class-validator 进行验证
安装依赖
bashnpm install class-validator class-transformer
创建 DTO 类
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; }
启用全局验证
typescriptimport { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe({ whitelist: true, // 自动移除未定义的属性 forbidNonWhitelisted: true, // 如果存在未定义的属性则抛出错误 transform: true, // 自动转换类型 transformOptions: { enableImplicitConversion: true, }, })); await app.listen(3000); } bootstrap();
常用验证装饰器
字符串验证
@IsString()- 必须是字符串@MinLength(min)- 最小长度@MaxLength(max)- 最大长度@Matches(pattern)- 匹配正则表达式@IsEmail()- 必须是有效的电子邮件@IsUrl()- 必须是有效的 URL@IsUUID()- 必须是有效的 UUID
数字验证
@IsInt()- 必须是整数@IsFloat()- 必须是浮点数@IsPositive()- 必须是正数@IsNegative()- 必须是负数@Min(value)- 最小值@Max(value)- 最大值
日期验证
@IsDate()- 必须是日期对象@MinDate(date)- 不早于指定日期@MaxDate(date)- 不晚于指定日期
其他验证
@IsBoolean()- 必须是布尔值@IsArray()- 必须是数组@IsEnum(enum)- 必须是枚举值@IsOptional()- 可选字段@IsDefined()- 必须定义(不能是 undefined 或 null)
自定义验证器
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) { // 自定义验证逻辑 return value === 'valid'; }, defaultMessage(args: ValidationArguments) { return `${args.property} must be valid`; }, }, }); }; } // 使用自定义验证器 export class CreateUserDto { @IsCustomProperty() customField: string; }
最佳实践
- 使用 DTO:为所有请求体创建数据传输对象
- 启用全局验证:在应用程序级别启用 ValidationPipe
- 使用白名单:启用
whitelist和forbidNonWhitelisted选项 - 自定义错误消息:为验证规则提供有意义的错误消息
- 组合验证器:使用多个验证器创建复杂的验证规则
- 测试验证:为 DTO 和管道编写测试
- 文档化:为 DTO 类添加清晰的文档注释
- 类型转换:启用自动类型转换以简化代码
总结
NestJS 管道和验证系统提供了:
- 强大的数据验证能力
- 灵活的数据转换机制
- 丰富的内置管道
- 易于扩展的自定义管道
- 与 class-validator 的无缝集成
掌握管道和验证是构建健壮、安全的 NestJS 应用程序的重要组成部分。通过合理使用管道和验证,可以确保数据的完整性和安全性,减少运行时错误,提高应用程序的可靠性。