当然!我们来探讨一个能极大提升你应用健壮性和安全性的核心技术:验证 (Validation)。这就像是为你的应用聘请了一位火眼金睛、一丝不苟的“门卫”,确保任何试图进入你系统的“访客”(数据)都必须符合规矩。
为你的应用聘请一位“火眼金睛”的门卫:NestJS 验证终极指南
想象你的应用是一家高档餐厅的厨房。
- 服务员(前端或客户端) 会将客人的**点菜单(传入的数据,如 JSON Body)**递送到厨房门口。
- 你(
Controller
的处理函数) 是主厨,准备根据点菜单大展身手。
但如果服务员递过来一张潦草的、不合规矩的点菜单怎么办?
- “我要一份‘一百二十’块牛排。” (
price
应该是number
,却传了string
) - “随便来点吃的。” (没有提供必需的菜品名称
name
) - “我要一份名字超级无敌长长长到写不下的惠灵顿牛排……” (
name
超过了数据库字段长度限制)
如果你作为主厨,不加检查就直接开始做菜,厨房肯定会乱成一锅粥,甚至可能引发事故(系统崩溃、存入脏数据)。
你需要一个专业的迎宾/门卫(Validation Pipe) 站在厨房门口。他会拿到每一份点菜单,用一本**《餐厅点餐规范手册》(DTO 和验证规则)** 来仔细核对:
- 必填项(菜名)写了吗?
- 价格是数字吗?
- 菜名长度在规定范围内吗?
只有完全合格的点菜单,才能被递到主厨手里。不合格的,门卫会直接退回给服务员并告知原因(返回一个清晰的 400 Bad Request
错误)。
在 NestJS 中,这个“门卫”就是 ValidationPipe
,而那本“规范手册”就是我们用 class-validator
和 class-transformer
这两个库来定义的 DTO (Data Transfer Object)。
1. 第一步:准备“规范手册” (DTO)
前置知识:什么是 DTO (数据传输对象)?
DTO 是一个专门用来在不同进程或应用层之间传输数据的对象。在 NestJS 中,我们通常使用类 (Class) 来定义 DTO。
为什么用 class
而不是 interface
? 因为 TypeScript 的 interface
在代码被编译成 JavaScript 后就消失了,我们无法在运行时获取它的信息。而 class
会被保留下来,这使得我们可以给它的属性附加装饰器 (Decorators),而这些装饰器正是我们定义验证规则的关键。
让我们来创建一个“创建猫猫”的点菜单 CreateCatDto
。
src/cats/dto/create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
```这只是一个骨架,接下来我们要为它添加规则。
### 2. 第二步:为“手册”编写规则 (使用 `class-validator`)
我们需要两个强大的“魔法工具”来编写规则:
* **`class-validator`**: 提供了海量的验证装饰器(如 `@IsString`, `@IsInt`, `@MinLength`),让你用声明式的方式来定义一个属性应该满足什么条件。
* **`class-transformer`**: 它的主要作用是将普通的 JavaScript 对象转换为我们定义的 DTO 类的实例,并可以自动进行一些类型转换。`ValidationPipe` 在后台会自动使用它。
**首先,安装它们:**
```bash
npm install class-validator class-transformer
现在,让我们用验证装饰器来丰富我们的 DTO:
src/cats/dto/create-cat.dto.ts
(添加规则后)
import { IsString, IsInt, Min, Max, MinLength } from 'class-validator';
export class CreateCatDto {
@IsString({ message: '猫猫的名字必须是字符串' }) // 验证 name 是不是字符串
@MinLength(2, { message: '名字太短了,至少需要2个字符' }) // 最小长度
name: string;
@IsInt() // 验证 age 是不是整数
@Min(0) // 最小值为 0
@Max(25) // 最大值为 25
age: number;
@IsString()
breed: string;
}
现在我们的“规范手册”已经非常详细和严格了。
3. 第三步:聘请“门卫” (ValidationPipe
)
有了手册,我们还需要一个执行者。NestJS 提供了一个内置的 ValidationPipe
,它能自动读取 DTO 上的验证装饰器并执行校验。
最简单、最推荐的聘请方式,就是让他成为一个全局的门卫,检查所有进入应用的请求。我们在 main.ts
中进行配置。
src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 聘请一位全局的门卫
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
就这样,一行代码,你的应用现在就有了一位全天候、高度负责的门卫了!
4. 第四步:观察“门卫”的工作流程
现在,让我们看看当点菜单(请求)递过来时,会发生什么。
src/cats/cats.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
@Post()
async create(@Body() createCatDto: CreateCatDto) {
// 如果代码能执行到这里,说明 createCatDto 100% 是通过了验证的
// 而且它已经是一个 CreateCatDto 的实例,而不是一个普通 JS 对象
console.log('点菜单合格,主厨开始做菜!', createCatDto);
return `This action adds a new cat named ${createCatDto.name}`;
}
}
场景 A:一份合格的点菜单
客户端发送 POST 请求到 /cats
,请求体为:
{
"name": "Garfield",
"age": 7,
"breed": "Exotic Shorthair"
}
流程:
- 请求到达 NestJS。
ValidationPipe
(全局门卫) 拦截请求体。- 门卫拿出
CreateCatDto
(规范手册),逐一核对:name
是字符串且长度>2?是。age
是整数且在 0-25 之间?是。breed
是字符串?是。 - 全部通过! 门卫将这份合格的点菜单递给了主厨(
create
方法)。 - 控制台打印 "点菜单合格...",客户端收到 201 成功响应。
场景 B:一份不合格的点菜单
客户端发送 POST 请求,请求体为:
{
"name": "G",
"age": "abc",
"breed": "Exotic Shorthair"
}
流程:
- 请求到达 NestJS。
ValidationPipe
开始核对。- 发现问题!
name
: "G" 的长度小于 2,违反@MinLength(2)
。age
: "abc" 不是整数,违反@IsInt()
。
- 不合格! 门卫直接拒绝这份点菜单,根本不会把它递给主厨。
ValidationPipe
自动抛出一个BadRequestException
异常。- NestJS 捕获这个异常,并给客户端返回一个格式清晰的 400 Bad Request 错误响应:
{
"statusCode": 400,
"message": ["名字太短了,至少需要2个字符", "age must be an integer number"],
"error": "Bad Request"
}
看,这个自动生成的错误信息多么友好和清晰!它直接告诉了客户端哪里填错了。
5. “门卫”的额外技能:数据转换
ValidationPipe
还有一个隐藏的超能力:自动类型转换。
比如,一个 GET 请求的查询参数总是字符串类型:GET /cats?age=5
。这里的 5
是一个字符串 '5'
。
如果我们开启转换功能,ValidationPipe
可以自动把它变成数字 5
。
main.ts
(开启转换)
app.useGlobalPipes(
new ValidationPipe({
transform: true, // 开启自动转换
})
);
现在,在 DTO 中,我们可以用 class-transformer
的 @Type
装饰器来辅助转换。
// find-cats.dto.ts
import { Type } from 'class-transformer';
import { IsInt, IsOptional } from 'class-validator';
export class FindCatsDto {
@IsInt()
@IsOptional() // 标记为可选参数
@Type(() => Number) // 告诉 ValidationPipe,请尝试将这个值转换为 Number
age?: number;
}
// cats.controller.ts
@Get()
findAll(@Query() query: FindCatsDto) {
// 因为开启了 transform,这里的 query.age 就已经是 number 类型了!
console.log(typeof query.age); // "number"
// ...
}
总结
验证是构建可靠 API 的第一道防线。NestJS 提供的 ValidationPipe
配合 class-validator
和 class-transformer
,形成了一套自动化、声明式、功能强大的验证工作流。
- 定义 DTO (class):用类作为数据传输的“规范手册”。
- 添加规则 (decorators):用
@IsString()
等装饰器在 DTO 上声明验证规则。 - 全局启用 (
ValidationPipe
):在main.ts
中全局启用ValidationPipe
,让“门卫”自动上岗。
遵循这个模式,你的 Controller 代码会变得极其整洁,因为它不再需要充斥着 if (typeof name !== 'string')
这样的手动校验逻辑。所有的验证工作都交给了这位可靠的“门卫”,让你可以安心地在“厨房”里专注于核心的业务逻辑。