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

NestJS 管道和验证如何工作?

2月17日 22:31

管道(Pipe)的概念

管道是 NestJS 中用于数据转换和验证的类。它们使用 @Injectable() 装饰器装饰,并实现 PipeTransform 接口。管道主要有两个用途:

  1. 转换:将输入数据转换为所需的格式
  2. 验证:验证输入数据,如果验证失败则抛出异常

管道的基本结构

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; } }

使用管道

在参数级别使用管道

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 {}

全局管道

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();

或者在模块中使用:

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 {}

内置管道

1. ValidationPipe

NestJS 提供的内置验证管道,通常与 class-validatorclass-transformer 一起使用。

typescript
import { 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); }

自定义管道

验证管道示例

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; } }

转换管道示例

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; } }

选项化管道示例

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; } } // 使用选项化管道 @Get(':id') findOne(@Param('id', new ParseIntPipe({ exceptionFactory: (error) => new BadRequestException(error) })) id: number) { return this.catsService.findOne(id); }

使用 class-validator 进行验证

安装依赖

bash
npm install class-validator class-transformer

创建 DTO 类

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; }

启用全局验证

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

自定义验证器

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) { // 自定义验证逻辑 return value === 'valid'; }, defaultMessage(args: ValidationArguments) { return `${args.property} must be valid`; }, }, }); }; } // 使用自定义验证器 export class CreateUserDto { @IsCustomProperty() customField: string; }

最佳实践

  1. 使用 DTO:为所有请求体创建数据传输对象
  2. 启用全局验证:在应用程序级别启用 ValidationPipe
  3. 使用白名单:启用 whitelistforbidNonWhitelisted 选项
  4. 自定义错误消息:为验证规则提供有意义的错误消息
  5. 组合验证器:使用多个验证器创建复杂的验证规则
  6. 测试验证:为 DTO 和管道编写测试
  7. 文档化:为 DTO 类添加清晰的文档注释
  8. 类型转换:启用自动类型转换以简化代码

总结

NestJS 管道和验证系统提供了:

  • 强大的数据验证能力
  • 灵活的数据转换机制
  • 丰富的内置管道
  • 易于扩展的自定义管道
  • 与 class-validator 的无缝集成

掌握管道和验证是构建健壮、安全的 NestJS 应用程序的重要组成部分。通过合理使用管道和验证,可以确保数据的完整性和安全性,减少运行时错误,提高应用程序的可靠性。

标签:NestJS