Skip to content
On this page

NestJS 管道 (Pipes)

NestJS 管道是一个类,它实现了 PipeTransform 接口。管道的主要职责是转换或验证传入应用程序的数据。管道接收输入数据,执行某种转换或验证操作,然后返回输出数据。

基础概念

管道在 NestJS 应用程序中扮演以下角色:

  1. 数据转换 - 转换输入数据为所需格式
  2. 数据验证 - 验证输入数据是否符合预期格式
  3. 数据清理 - 清理或规范化输入数据

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

与守卫、拦截器的关系

管道在请求处理流程中的位置:

  1. 中间件 - 最先执行
  2. 守卫 - 权限控制
  3. 拦截器 - 请求/响应转换
  4. 管道 - 数据验证和转换
  5. 控制器方法 - 处理请求

总结

NestJS 管道是处理输入数据验证和转换的强大工具。它们确保只有有效和正确格式的数据才能进入控制器方法,从而提高应用程序的健壮性和安全性。

管道的主要特点:

  • 实现 PipeTransform 接口
  • 可以验证和转换输入数据
  • 可以在参数级别、方法级别、控制器级别或全局级别应用
  • 支持同步和异步操作
  • 与 NestJS 的依赖注入系统集成