Appearance
NestJS 管道 (Pipes)
NestJS 管道是一个类,它实现了 PipeTransform 接口。管道的主要职责是转换或验证传入应用程序的数据。管道接收输入数据,执行某种转换或验证操作,然后返回输出数据。
基础概念
管道在 NestJS 应用程序中扮演以下角色:
- 数据转换 - 转换输入数据为所需格式
- 数据验证 - 验证输入数据是否符合预期格式
- 数据清理 - 清理或规范化输入数据
NestJS 提供了多种内置管道,同时也支持自定义管道。
管道接口
每个管道必须实现 PipeTransform 接口:
export interface PipeTransform<T = any> {
transform(value: T, metadata: ArgumentMetadata): any;
}
value- 将要转换的输入值metadata- 参数的元数据,包含参数类型、属性类型等信息
创建自定义管道
让我们创建一个简单的参数验证管道:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (!value) {
throw new BadRequestException('Value is required');
}
return value;
}
}
内置管道
NestJS 提供了多个内置管道:
1. ParseIntPipe
将字符串转换为整数:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
2. ParseFloatPipe
将字符串转换为浮点数:
@Get('price/:value')
getPrice(@Param('value', ParseFloatPipe) value: number) {
return this.productService.getPrice(value);
}
3. ParseBoolPipe
将字符串转换为布尔值:
@Get('active/:status')
getActive(@Param('status', ParseBoolPipe) status: boolean) {
return this.userService.getActiveUsers(status);
}
参数验证管道
创建一个更复杂的验证管道:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class CustomValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
使用管道
管道可以通过多种方式应用:
1. 在控制器方法中
@Post()
async create(
@Body(CustomValidationPipe) createCatDto: CreateCatDto,
) {
return this.catsService.create(createCatDto);
}
2. 在参数装饰器中
@Get(':id')
findOne(
@Param('id', ParseIntPipe, CustomValidationPipe) id: number,
) {
return this.catsService.findOne(id);
}
3. 在类级别
@UsePipes(new CustomValidationPipe())
export class CatsController {
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
}
4. 全局注册
在主应用中注册全局管道:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
参数类型验证
使用 class-validator 和 class-transformer 进行复杂验证:
// create-cat.dto.ts
import { IsString, IsInt, Min, Max, IsEmail, IsOptional } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
@Max(200)
age: number;
@IsString()
@IsOptional()
breed?: string;
}
typescript
// validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || this.isNativeType(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
const messages = errors.map(error => Object.values(error.constraints)).flat();
throw new BadRequestException(messages);
}
return value;
}
private isNativeType(metatype: Function): boolean {
const nativeTypes = [String, Boolean, Number, Array, Object];
return nativeTypes.includes(metatype);
}
}
转换管道
创建一个数据转换管道:
@Injectable()
export class ToUppercasePipe implements PipeTransform<string> {
transform(value: string) {
return value.toUpperCase();
}
}
// 使用
@Post()
async create(@Body('name', ToUppercasePipe) name: string) {
// name 将会是大写形式
return `Hello ${name}`;
}
数组验证
验证数组参数:
@Injectable()
export class ParseArrayPipe implements PipeTransform {
transform(value: string | string[]) {
if (Array.isArray(value)) {
return value;
}
return [value];
}
}
// 使用
@Get()
findAll(@Query('ids', ParseArrayPipe) ids: string[]) {
return this.service.findByIds(ids);
}
异步管道
管道可以是异步的:
@Injectable()
export class AsyncValidationPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
// 模拟异步验证
await new Promise(resolve => setTimeout(resolve, 100));
if (value.name === 'forbidden') {
throw new BadRequestException('Name is forbidden');
}
return value;
}
}
管道最佳实践
1. 组合多个管道
@Post()
async create(
@Body(
new ValidationPipe(),
new CustomValidationPipe(),
new SanitizePipe()
) createCatDto: CreateCatDto,
) {
return this.catsService.create(createCatDto);
}
2. 验证查询参数
@Get()
findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
return this.catsService.paginate(page, limit);
}
3. 自定义错误处理
@Injectable()
export class CustomValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const { type, metatype } = metadata;
if (!value) {
switch (type) {
case 'body':
throw new BadRequestException('Body cannot be empty');
case 'param':
throw new BadRequestException('Parameter is required');
case 'query':
throw new BadRequestException('Query parameter is required');
default:
return value;
}
}
return value;
}
}
与守卫、拦截器的关系
管道在请求处理流程中的位置:
- 中间件 - 最先执行
- 守卫 - 权限控制
- 拦截器 - 请求/响应转换
- 管道 - 数据验证和转换
- 控制器方法 - 处理请求
总结
NestJS 管道是处理输入数据验证和转换的强大工具。它们确保只有有效和正确格式的数据才能进入控制器方法,从而提高应用程序的健壮性和安全性。
管道的主要特点:
- 实现 PipeTransform 接口
- 可以验证和转换输入数据
- 可以在参数级别、方法级别、控制器级别或全局级别应用
- 支持同步和异步操作
- 与 NestJS 的依赖注入系统集成