NestJS管道(Pipe) #

什么是管道? #

管道是用@Injectable()装饰器装饰的类,它实现了PipeTransform接口。管道有两个主要用途:

  1. 数据转换:将输入数据转换为所需格式
  2. 数据验证:验证输入数据,如果有效则传递,否则抛出异常

内置管道 #

NestJS提供了以下内置管道:

管道 说明
ValidationPipe 数据验证
ParseIntPipe 转换为整数
ParseFloatPipe 转换为浮点数
ParseBoolPipe 转换为布尔值
ParseArrayPipe 转换为数组
ParseUUIDPipe 验证UUID
ParseEnumPipe 验证枚举值
DefaultValuePipe 设置默认值

使用内置管道 #

ParseIntPipe #

typescript
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return `User ID: ${id}, Type: ${typeof id}`;
  }
}

ParseUUIDPipe #

typescript
import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id', ParseUUIDPipe) id: string) {
    return `User UUID: ${id}`;
  }
}

ParseArrayPipe #

typescript
import { Controller, Get, Query, ParseArrayPipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findByIds(
    @Query('ids', ParseArrayPipe) ids: number[],
  ) {
    return ids;
  }
}

DefaultValuePipe #

typescript
import { Controller, Get, Query, DefaultValuePipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
  ) {
    return { page, limit };
  }
}

可选管道 #

typescript
@Get(':id')
findOne(
  @Param('id', new ParseIntPipe({ optional: true })) id?: number,
) {
  return id;
}

ValidationPipe #

安装依赖 #

bash
npm install class-validator class-transformer

配置全局验证 #

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

验证选项 #

选项 说明
whitelist 过滤未定义的属性
forbidNonWhitelisted 拒绝未定义的属性
transform 自动类型转换
disableErrorMessages 禁用错误信息
validationError.target 是否包含目标对象
validationError.value 是否包含错误值

使用DTO验证 #

typescript
import {
  IsString,
  IsEmail,
  IsInt,
  Min,
  Max,
  IsOptional,
  IsNotEmpty,
  MinLength,
  MaxLength,
  IsDateString,
  IsEnum,
} from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  @MinLength(2)
  @MaxLength(50)
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  @Min(0)
  @Max(150)
  age: number;

  @IsOptional()
  @IsString()
  bio?: string;

  @IsEnum(['male', 'female', 'other'])
  gender: string;

  @IsDateString()
  birthday: string;
}

控制器中使用 #

typescript
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return createUserDto;
  }
}

嵌套对象验证 #

typescript
import { ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';

class AddressDto {
  @IsString()
  street: string;

  @IsString()
  city: string;
}

export class CreateUserDto {
  @IsString()
  name: string;

  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

数组验证 #

typescript
import { IsArray, ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';

class TagDto {
  @IsString()
  name: string;
}

export class CreatePostDto {
  @IsString()
  title: string;

  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => TagDto)
  tags: TagDto[];
}

自定义管道 #

创建自定义管道 #

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,
  BadRequestException,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } 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 = plainToInstance(metatype, value);
    const errors = await validate(object);

    if (errors.length > 0) {
      const messages = errors.map(error => ({
        property: error.property,
        constraints: error.constraints,
      }));

      throw new BadRequestException({
        message: 'Validation failed',
        errors: messages,
      });
    }

    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

数据转换管道 #

typescript
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class TrimPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (typeof value === 'string') {
      return value.trim();
    }

    if (typeof value === 'object' && value !== null) {
      return this.trimObject(value);
    }

    return value;
  }

  private trimObject(obj: any): any {
    const result = {};
    for (const key in obj) {
      if (typeof obj[key] === 'string') {
        result[key] = obj[key].trim();
      } else if (typeof obj[key] === 'object') {
        result[key] = this.trimObject(obj[key]);
      } else {
        result[key] = obj[key];
      }
    }
    return result;
  }
}

管道应用范围 #

参数级别 #

typescript
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}

方法级别 #

typescript
@Post()
@UsePipes(ValidationPipe)
create(@Body() createUserDto: CreateUserDto) {}

控制器级别 #

typescript
@Controller('users')
@UsePipes(ValidationPipe)
export class UsersController {}

全局级别 #

typescript
app.useGlobalPipes(new ValidationPipe());

管道执行顺序 #

text
请求 → 中间件 → 守卫 → 拦截器(前) → 管道 → 控制器

管道最佳实践 #

1. 使用DTO定义验证规则 #

typescript
export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsEmail()
  email: string;
}

2. 分离创建和更新DTO #

typescript
export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  password: string;
}

export class UpdateUserDto extends PartialType(CreateUserDto) {
  // 所有字段可选
}

3. 使用自定义错误消息 #

typescript
import { IsString, IsEmail, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString({ message: 'Name must be a string' })
  @MinLength(2, { message: 'Name must be at least 2 characters' })
  name: string;

  @IsEmail({}, { message: 'Invalid email format' })
  email: string;
}

4. 组合多个管道 #

typescript
@Get()
findAll(
  @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
) {}

总结 #

本章学习了NestJS管道:

  • 管道的概念和用途
  • 内置管道的使用
  • ValidationPipe配置
  • 自定义管道创建
  • 管道应用范围

接下来,让我们学习 守卫(Guard)

最后更新:2026-03-28