1-5 进阶 NestJS 内置管道、Transform 与自定义管道用法
本节深入介绍 NestJS 的参数级管道,包括内置转换管道(ParseIntPipe、ParseArrayPipe 等)、嵌套数据的校验与转换(@Transform、each: true),以及自定义管道的完整创建流程。
内置转换管道概览
内置管道主要用于参数级别的数据类型转换,将 HTTP 请求中的字符串参数转为目标类型:
| 管道 | 功能 | 适用场景 |
|---|---|---|
ParseIntPipe | 字符串 → 整数 | 路由参数 @Param('id') |
ParseFloatPipe | 字符串 → 浮点数 | 价格、坐标等参数 |
ParseBoolPipe | 字符串 → 布尔值 | 开关参数 ?active=true |
ParseArrayPipe | 字符串 → 数组 | 多值查询参数 |
ParseUUIDPipe | 校验 UUID 格式 | 主键为 UUID 的路由参数 |
ParseEnumPipe | 校验枚举值 | 状态筛选参数 |
DefaultValuePipe | 设置默认值 | 分页 ?page=1 |
ParseIntPipe:字符串转整数
GET 请求的 query 参数默认都是字符串类型,使用 ParseIntPipe 自动转换:
// user/user.controller.ts
import { Controller, Get, Query, ParseIntPipe } from '@nestjs/common';
@Controller('users')
export class UserController {
@Get()
findUser(@Query('id', ParseIntPipe) id: number) {
console.log(typeof id, id); // number 100
return { id };
}
}
typescript
完整写法(带参数):new ParseIntPipe({ errorHttpStatusCode: HttpStatus.BAD_REQUEST })
ParseIntPipe不加new是简写形式,等价于new ParseIntPipe()。当管道构造函数需要传参时必须使用new。
ParseArrayPipe:请求体转数组
当 POST 请求体是数组结构时,配合 DTO 使用:
import { Body, Controller, Post, ParseArrayPipe } from '@nestjs/common';
@Controller('auth')
export class AuthController {
@Post('batch-signup')
batchSignUp(
@Body(new ParseArrayPipe({ items: SignInUserDto }))
dtos: SignInUserDto[],
) {
console.log(dtos); // SignInUserDto[]
return dtos;
}
}
typescript
请求示例:
[
{ "username": "user1", "password": "123456" },
{ "username": "user2", "password": "654321" }
]
json
嵌套数据的校验与转换
当 DTO 中包含数组类型的嵌套字段时,需要特殊处理校验和转换。
数组元素的类型校验(each: true)
// auth/dto/sign-in-user.dto.ts
import {
IsOptional,
IsArray,
IsNumber,
IsNotEmpty,
IsString,
Length,
} from 'class-validator';
export class SignInUserDto {
@IsNotEmpty({ message: '用户名不得为空' })
@IsString({ message: '用户名必须是一个字符串' })
@Length(6, 20)
username: string;
@IsNotEmpty({ message: '密码不得为空' })
@IsString({ message: '密码必须是一个字符串' })
@Length(6, 32)
password: string;
@IsOptional()
@IsArray({ message: 'roles 必须是数组' })
@IsNumber({}, { each: true, message: 'roles 中每个元素必须是 number 类型' })
roles: number[];
}
typescript
each: true 的作用:对数组中的每一个元素都应用该校验规则,而非仅校验数组本身。
请求示例:
{
"username": "testuser",
"password": "123456",
"roles": [1, 2, 3]
}
json
如果传入 roles: [1, "2", 3],校验将失败并返回错误消息。
@Transform 装饰器:数据转换
class-transformer 提供的 @Transform 装饰器可以在 DTO 层直接对数据进行转换:
import { Transform } from 'class-transformer';
import { IsOptional, IsArray, IsNumber } from 'class-validator';
export class SignInUserDto {
// ... 其他字段
@IsOptional()
@IsArray()
@Transform(({ value }) => {
if (!value) return value;
return value.map((item: string) => parseInt(item, 10));
})
roles: number[];
}
typescript
@Transform 工作原理:
请求数据 { roles: ["1", "2", "3"] }
│
▼ @Transform 执行
转换后数据 { roles: [1, 2, 3] }
│
▼ class-validator 校验
校验通过,进入 Controller
text
@Transform不仅适用于请求参数,在响应数据序列化时也非常有用。
自定义管道
当内置管道无法满足需求时,可以创建自定义管道。
使用 CLI 生成管道
nest g pipe create-user --flat --no-spec
bash
--flat:不创建子目录,直接生成在当前目录--no-spec:不生成测试文件
实现自定义管道
自定义管道必须实现 PipeTransform 接口:
// auth/pipes/create-user.pipe.ts
import { PipeTransform, Injectable } from '@nestjs/common';
@Injectable()
export class CreateUserPipe implements PipeTransform {
transform(value: any) {
// value 即为请求体中的数据
if (!value) return value;
// 对嵌套的 roles 数组进行类型转换
if (value.roles && Array.isArray(value.roles)) {
value.roles = value.roles.map((item: string) => parseInt(item, 10));
}
return value;
}
}
typescript
在 Controller 中使用自定义管道
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserPipe } from './pipes/create-user.pipe';
import { SignInUserDto } from './dto/sign-in-user.dto';
@Controller('auth')
export class AuthController {
@Post('signup')
signUp(@Body(new CreateUserPipe()) dto: SignInUserDto) {
console.log(dto); // roles 已被转换为 number[]
return dto;
}
}
typescript
PipeTransform 接口签名:
interface PipeTransform<T = any, R = any> {
transform(value: T, metadata: ArgumentMetadata): R;
}
interface ArgumentMetadata {
type: 'body' | 'query' | 'param' | 'custom';
metatype: Type<unknown> | undefined;
data: string | undefined;
}
typescript
value— 待处理的输入数据metadata.type— 参数来源(body / query / param / custom)metadata.metatype— 参数的声明类型(如 SignInUserDto)metadata.data— 传递给装饰器的字符串参数
三种转换方案对比
| 方案 | 适用场景 | 复杂度 | 灵活性 |
|---|---|---|---|
| 内置管道(ParseIntPipe 等) | 单参数类型转换 | 低 | 仅限预定义类型 |
@Transform 装饰器 | DTO 属性级转换 | 中 | 可自定义转换逻辑 |
| 自定义管道(PipeTransform) | 复杂数据结构处理 | 高 | 完全自定义,可复用 |
whitelist 安全验证
在 main.ts 中设置 whitelist: true 后,请求体中未在 DTO 中定义的属性会被自动剥离:
// main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 剥离 DTO 中未定义的字段
}),
);
typescript
效果验证:
请求体:{ username: "test", password: "123456", role: "admin" }
│
▼ whitelist: true
实际接收:{ username: "test", password: "123456" }
// "role" 被自动剥离
text
若需接收新字段,必须在 DTO 中显式声明:
export class SignInUserDto {
// ... 已有字段
@IsOptional()
@IsArray()
@IsNumber({}, { each: true })
roles: number[];
}
typescript
本节要点
- 内置管道:
ParseIntPipe、ParseFloatPipe、ParseBoolPipe、ParseArrayPipe等用于参数级类型转换 each: true:对数组属性中的每个元素应用校验规则,适用于嵌套数据校验@Transform:class-transformer 提供的属性级转换装饰器,轻量灵活- 自定义管道:实现
PipeTransform接口,处理复杂数据结构的转换逻辑 - whitelist 安全:
whitelist: true自动剥离未定义字段,防止参数注入
↑