Skip to content

NestJS 管道

基础语法

  • 用 @Injectable() 装饰类;
  • 实现 PipeTransform 接口;
  • 实现 transform 方法(处理转换 / 验证逻辑);

参数说明

shell
# 核心方法
transform(value: any, metadata: ArgumentMetadata): any | Promise<any>

# 参数说明
value:待处理的参数值(如 URL 参数、请求体、查询参数)
metadata:参数元数据,包含:
  type:参数类型(body/query/param/custom)
  metatype:参数的类型(如 String/Number/ 自定义 DTO)
  data:参数名(如 @Param('id') 中的 id)

验证型管道

聚焦单个参数的精细化验证,同步验证管道(手机号格式校验)

  1. 定义管道
ts
// src/common/pipes/phone-number.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class PhoneNumberPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // 仅处理 @Body('phone') 对应的参数
    if (metadata.type === 'body' && metadata.data === 'phone') {
      const phoneRegex = /^1[3-9]\d{9}$/;
      if (!value || !phoneRegex.test(value)) {
        throw new BadRequestException('手机号格式错误(需为11位中国大陆手机号)');
      }
    }
    // 合法则透传值
    return value;
  }
}
  1. 使用管道
ts
@Post()
create(
  @Body('phone', PhoneNumberPipe) phone: string,
  @Body() createUserDto: CreateUserDto,
) {
  return { phone, createUserDto };
}

转换型管道

将前端传入的日期字符串(如 '2025-12-03')转换为 Date 对象

  1. 定义管道
ts
// src/common/pipes/parse-date.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseDatePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value) return value;

    const date = new Date(value);
    if (isNaN(date.getTime())) {
      throw new BadRequestException(`无效的日期格式:${value}`);
    }
    return date;
  }
}
  1. 使用管道
ts
@Get('stats')
getStats(@Query('startDate', ParseDatePipe) startDate: Date) {
  return { startDate, type: typeof startDate }; // startDate 为 Date 实例
}

异步管道

transform 方法支持返回 Promise,适用于需要异步操作的场景(如数据库校验)

  1. 定义管道
ts
// src/user/pipes/user-exists.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, NotFoundException } from '@nestjs/common';
import { UserService } from '../user.service';

@Injectable()
export class UserExistsPipe implements PipeTransform {
  // 依赖注入 UserService(管道支持完整的依赖注入能力)
  constructor(private readonly userService: UserService) {}

  async transform(value: string, metadata: ArgumentMetadata) {
    // 仅处理 @Param('userId') 对应的参数
    if (metadata.type === 'param' && metadata.data === 'userId') {
      const user = await this.userService.findById(value);
      if (!user) {
        throw new NotFoundException(`用户 ${value} 不存在`);
      }
    }
    return value;
  }
}
  1. 使用管道
ts
@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':userId')
  findOne(@Param('userId', UserExistsPipe) userId: string) {
    return this.userService.findById(userId);
  }
}

内置管道

  1. 内置管道
shell
# 转换 & 验证
ParseIntPipe 将字符串参数转换为整数,失败则抛 400 异常
ParseFloatPipe 将字符串参数转换为浮点数;
ParseBoolPipe 将字符串参数转换为布尔值(支持 'true'/'false''1'/'0' 等)
ParseArrayPipe 将字符串转换为数组,转化不了则报异常

# 验证
ValidationPipe 基于 class-validator 校验 DTO 字段(最常用)
ParseUUIDPipe 校验参数是否为 UUID(支持指定版本)

# 默认值
DefaultValuePipe 为未传递的参数设置默认值(需配合其他管道使用)
  1. 使用
ts
import { Controller, Get, Param, Query, ParseIntPipe, DefaultValuePipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  // 路由参数转换为整数
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return { id, type: typeof id }; // id 已确保是 number 类型
  }

  // 查询参数设置默认值 + 转换为整数
  @Get()
  findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number
  ) {
    return { page, size };
  }
}

管道执行顺序

全局管道 → 模块管道 → 控制器管道 → 方法管道 → 参数管道;

同级别多个管道按注册顺序执行;

管道执行是串行的,前一个管道的输出是后一个管道的输入。

ts
// 执行顺序:PipeA → PipeB → ParseIntPipe。

// 自定义管道 A
@Injectable()
export class PipeA implements PipeTransform {
  transform(value: any) {
    console.log('PipeA 执行');
    return value + '_A';
  }
}

// 自定义管道 B
@Injectable()
export class PipeB implements PipeTransform {
  transform(value: any) {
    console.log('PipeB 执行');
    return value + '_B';
  }
}

// 控制器使用
@Controller('test')
@UsePipes(PipeA) // 控制器级别
export class TestController {
  @Get()
  @UsePipes(PipeB) // 方法级别
  test(@Query('name', ParseIntPipe) name: string) {
    console.log('最终值:', name);
    return name;
  }
}