管道 (Pipes) 与数据验证
管道在入参进入处理器前做转换与校验。见 guards 之前的请求链。
管道(Pipe)在 NestJS 中用于处理请求数据(如请求体、查询参数、路由参数等)的转换和验证。它在数据被控制器方法处理之前执行,可以:
- 转换:将输入数据转换为所需的类型(如字符串转数字)。
- 验证:检查输入数据是否符合预设规则,若不符合则抛出异常(通常是
BadRequestException)。
一、内置管道
NestJS 提供了一系列内置管道,覆盖常见的数据转换和验证场景。
| 管道 | 作用 | 使用示例 |
|---|---|---|
ValidationPipe | 基于 DTO 类和 class-validator 装饰器进行验证 | @Body(ValidationPipe) dto: CreateUserDto |
ParseIntPipe | 将字符串转换为整数,失败则抛出 BadRequestException | @Param('id', ParseIntPipe) id: number |
ParseFloatPipe | 将字符串转换为浮点数 | @Query('price', ParseFloatPipe) price: number |
ParseBoolPipe | 将字符串转换为布尔值('true'/'false') | @Query('active', ParseBoolPipe) active: boolean |
ParseUUIDPipe | 验证字符串是否为 UUID 格式(支持版本指定) | @Param('uuid', new ParseUUIDPipe({ version: '4' })) uuid: string |
ParseEnumPipe | 验证字符串是否属于指定的枚举值 | @Query('role', new ParseEnumPipe(RoleEnum)) role: RoleEnum |
DefaultValuePipe | 为缺失的参数提供默认值(常与 ParseIntPipe 等配合) | @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number |
示例:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
// id 已经确保为 number 类型,且是有效整数
return this.service.findOne(id);
}
二、数据验证:ValidationPipe + class-validator
ValidationPipe 是 NestJS 中最强大的验证管道,它使用 class-validator 和 class-transformer 库,通过 DTO(数据传输对象)类中的装饰器声明验证规则。
1. 安装依赖
npm install class-validator class-transformer
2. 定义 DTO 类并添加验证装饰器
import { IsString, IsEmail, IsInt, Min, Max, IsOptional } from "class-validator";
export class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(18)
@Max(120)
age: number;
@IsOptional()
@IsString()
bio?: string;
}
3. 在控制器中使用 ValidationPipe
@Post()
create(@Body(ValidationPipe) createUserDto: CreateUserDto) {
// createUserDto 已经通过验证,类型安全
return this.usersService.create(createUserDto);
}
如果请求数据不满足验证规则,NestJS 会自动返回 400 Bad Request,并包含详细的错误信息(哪些字段违反了哪些规则)。
4. ValidationPipe 常用选项
在 ValidationPipe 可以配置多种行为来优化验证过程和安全性。
| 选项 | 类型 | 描述 |
|---|---|---|
whitelist | boolean | 自动剥离 DTO 中未定义装饰器的属性(防止多余字段注入) |
forbidNonWhitelisted | boolean | 如果存在白名单之外的属性,直接抛错(拒绝请求) |
transform | boolean | 自动将请求数据转换为 DTO 类的实例(类型转换) |
disableErrorMessages | boolean | 生产环境隐藏详细错误信息(只返回字段错误列表) |
skipMissingProperties | boolean | 是否忽略未出现在请求中的可选字段(默认 false) |
配置示例(全局或局部):
@Post()
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))
create(@Body() createUserDto: CreateUserDto) {
// ...
}
whitelist: true:自动去除 DTO 中未定义装饰器的属性。forbidNonWhitelisted: true:当请求包含未定义的属性时,拒绝请求并返回错误。transform: true:将请求的普通对象转换为 DTO 类的实例,便于类型转换(例如字符串"18"自动转为数字18)。
三、创建自定义管道
当内置管道无法满足需求时,可以实现 PipeTransform 接口创建自定义管道。
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from "@nestjs/common";
@Injectable()
export class CustomParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException(`Invalid integer: ${value}`);
}
return val;
}
}
transform 方法的参数:
value:传入的原始值(如查询字符串、参数值)。metadata:包含type(参数类型,如'body'、'query'、'param')、metatype(DTO 类)、data(参数名)等信息。
使用自定义管道:
@Get(':id')
findOne(@Param('id', CustomParseIntPipe) id: number) {
// id 已经是有效整数
}
四、全局管道注册
为减少重复代码,可以将 ValidationPipe 或其他管道注册为全局管道,自动应用于所有入参。
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(3000);
}
bootstrap();
全局管道会作用于所有控制器的方法参数(包括 @Body()、@Query()、@Param())。但也可通过 @UsePipes() 在局部覆盖或组合。
注意:全局管道如果使用 ValidationPipe,需要确保 class-validator 和 class-transformer 已安装。
五、数据转换的另一种方式:@Transform()
class-transformer 提供的 @Transform() 装饰器可以在 DTO 类中进行更高级的数据转换。
import { Transform } from "class-transformer";
export class CreateUserDto {
@Transform(({ value }) => value.trim())
@IsString()
name: string;
}
通过 transform: true 开启 ValidationPipe 的转换功能后,上述转换会自动执行。
六、最佳实践
- 使用 DTO + class-validator:将验证规则声明在 DTO 中,保持控制器整洁。
- 开启
whitelist和forbidNonWhitelisted:防止未预期的属性注入,增强安全性。 - 开启
transform:自动类型转换,避免手动parseInt等操作。 - 全局注册
ValidationPipe:避免在每个控制器重复写@Body(ValidationPipe)。 - 自定义管道用于复杂转换:例如从数据库加载实体、数据格式解析等。
总结
| 概念 | 作用 |
|---|---|
ParseIntPipe 等 | 简单类型转换 |
ValidationPipe + class-validator | 基于 DTO 的强大验证 |
whitelist / forbidNonWhitelisted | 防止多余字段注入 |
transform: true | 自动类型转换 |
| 自定义管道 | 复用复杂的转换/验证逻辑 |
| 全局管道 | 统一应用所有请求的验证规则 |
通过合理使用管道,可以大幅减少控制器中的样板代码,提升应用的数据安全性和可维护性。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Pipes | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。