Skip to content

NestJS 自定义管道使用场景

转换:字符串转数字

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

@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException(`"${value}" 不是有效的数字`);
    }
    return val;
  }
}
  1. 使用
ts
// src/controllers/user.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { ParseIntPipe } from '../pipes/parse-int.pipe';

@Controller('users')
export class UserController {
  // URL参数id强制转为数字
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return { id, message: `获取用户 ${id} 成功` };
  }
}

验证:结构化数据验证

处理 POST/PUT 请求体(Body)的结构化验证(如必填项、格式、长度),是最常用的管道场景。Nest 内置 ValidationPipe,结合 class-validator 实现。

  1. 安装依赖
shell
npm install class-validator class-transformer --save
  1. 定义 DTO 并添加验证规则
ts
// src/dtos/create-user.dto.ts
import { IsString, IsEmail, MinLength, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty({ message: '用户名不能为空' })
  @MinLength(3, { message: '用户名长度≥3' })
  username: string;

  @IsEmail({}, { message: '邮箱格式错误' })
  email: string;

  @IsString()
  @MinLength(6, { message: '密码长度≥6' })
  password: string;
}
  1. 使用验证管道
ts
// src/controllers/user.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from '../dtos/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  @UsePipes(
    new ValidationPipe({
      whitelist: true, // 过滤DTO中未定义的属性
      forbidNonWhitelisted: true, // 存在未知属性时抛异常
      transform: true, // 自动将请求体转为DTO实例
    })
  )
  create(@Body() createUserDto: CreateUserDto) {
    return { message: '创建成功', data: createUserDto };
  }
}

验证:年龄验证

  1. 定义年龄验证管道(仅允许≥18 岁)
ts
// src/pipes/age-validation.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class AgeValidationPipe implements PipeTransform {
  transform(value: any) {
    const age = parseInt(value, 10);
    if (isNaN(age)) throw new BadRequestException('年龄必须是数字');
    if (age < 18) throw new BadRequestException('年龄必须≥18岁');
    return age;
  }
}
  1. 使用
ts
@Post('register')
createUser(
  @Body('age', AgeValidationPipe) age: number,
  @Body() createUserDto: CreateUserDto,
) {
  return { ...createUserDto, age };
}

验证:订单状态

  1. 定义订单状态验证管道(仅支持指定状态)
ts
// src/pipes/order-status-validation.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

const ALLOWED_STATUSES = ['pending', 'paid', 'shipped', 'completed'];

@Injectable()
export class OrderStatusValidationPipe implements PipeTransform {
  transform(value: any) {
    if (!ALLOWED_STATUSES.includes(value)) {
      throw new BadRequestException(`状态仅支持:${ALLOWED_STATUSES.join(', ')}`);
    }
    return value;
  }
}
  1. 使用
ts
// src/controllers/order.controller.ts
import { Controller, Patch, Param, Body } from '@nestjs/common';
import { OrderStatusValidationPipe } from '../pipes/order-status-validation.pipe';
import { ParseIntPipe } from '../pipes/parse-int.pipe';

@Controller('orders')
export class OrderController {
  @Patch(':id/status')
  updateStatus(
    @Param('id', ParseIntPipe) id: number,
    @Body('status', OrderStatusValidationPipe) status: string
  ) {
    return { id, status, message: `订单状态更新为 ${status}` };
  }
}

自定义错误处理

默认的 BadRequestException 无法满足自定义错误格式(如错误码),需在管道中抛出自定义业务异常

  1. 自定义错误异常
ts
// src/exceptions/business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';

export class BusinessException extends HttpException {
  constructor(
    message: string,
    private readonly errorCode: number
  ) {
    super({ message, errorCode }, HttpStatus.BAD_REQUEST);
  }
}
  1. 管道中使用自定义异常
ts
// src/pipes/phone-validation.pipe.ts
import { PipeTransform, Injectable } from '@nestjs/common';
import { BusinessException } from '../exceptions/business.exception';

const PHONE_REGEX = /^1[3-9]\d{9}$/; // 手机号正则

@Injectable()
export class PhoneValidationPipe implements PipeTransform {
  transform(value: any) {
    if (!PHONE_REGEX.test(value)) {
      throw new BusinessException('手机号格式错误', 10001); // 自定义错误码
    }
    return value;
  }
}
  1. 使用
ts
@Post('bind-phone')
bindPhone(@Body('phone', PhoneValidationPipe) phone: string) {
  return { message: '绑定成功', phone };
}

// 结果:
// {
//   "message": "手机号格式错误",
//   "errorCode": 10001
// }