白筱汐

想都是问题,做都是答案

0%

Nestjs 和 Prisma 实现 Restful Api:参数验证与转换

该文章内容在文章 Nestjs 和 Prisma 实现 Restful Api的基础上实现,如有需要,请先搜索查看基础文章

第二章

参数验证与转换

为了执行输入验证,您将使用NestJS Pipes。管道对路由处理程序正在处理的参数进行操作。Nest在路由处理程序之前调用一个管道,该管道接收用于路由处理程序的参数。管道可以做很多事情,比如验证输入、向输入添加字段等等。管道类似于中间件,但管道的作用域仅限于处理输入参数。NestJS提供了一些开箱即用的管道,但是您也可以创建自己的管道

管道有两个典型的用例:

  • 验证:评估输入的数据,如果有效,则不加修改地传递;否则,当数据不正确时抛出异常。
  • 转换:将输入数据转换为所需的形式(例如,从字符串转换为整数)。

全局设置ValidationPipe

在 NestJS 中,可以使用内置的 ValidationPipe 来进行输入验证。ValidationPipe 提供了一种便捷的方法,能够强制对所有来自客户端的请求数据进行验证。验证规则通过 class-validator 包的装饰器来声明,装饰器用于定义 DTO 类中的验证规则,以确保传入的数据符合预期的格式和类型。
首先我们需要安装2个库

1
pnpm install class-validator class-transformer

main.ts 引入 ValidationPipe,然后使用 app.useGlobalPipes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

app.useGlobalPipes(new ValidationPipe());

const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(3000);
}
bootstrap();

向CreateArticleDto添加验证规则

使用 class-validator 库给 CreateArticleDto 添加验证装饰器。

打开 src/articles/dto/create-article.dto.ts 文件,替换成以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/articles/dto/create-article.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
MinLength,
} from 'class-validator';

export class CreateArticleDto {
@IsString()
@IsNotEmpty()
@MinLength(5)
@ApiProperty()
title: string;

@IsString()
@IsOptional() // 字段可以不存在
@IsNotEmpty() // 如果存在不能为空
@MaxLength(300)
@ApiProperty({ required: false })
description?: string;

@IsString()
@IsNotEmpty()
@ApiProperty()
body: string;

@IsBoolean()
@IsOptional()
@ApiProperty({ required: false, default: false })
published?: boolean = false;
}

articles.service 中 create 方法接受的参数就是 CreateArticleDto 类型,我们来新增一篇文章来测试一下。

1
2
3
4
5
6
{
"title": "Temp",
"description": "Learn about input validation",
"body": "Input validation is...",
"published": false
}

接口返回的内容如下:

1
2
3
4
5
6
7
{
"message": [
"title must be longer than or equal to 5 characters"
],
"error": "Bad Request",
"statusCode": 400
}

这说明添加的验证规则生效了。客户端发起了一个 post 请求,ValidationPipe 验证参数未通过直接就返回给前端了,并没有到后续的路由。

从客户端请求中删除不必要的属性

如果我们在新建文章的时候加入 CreateArticleDto 中未定义的其他的属性,也是能新增成功的。但这很有可能会造成错误,比如新增下面这样的数据

1
2
3
4
5
6
7
8
{
"title": "example-title",
"description": "example-description",
"body": "example-body",
"published": true,
"createdAt": "2010-06-08T18:20:29.309Z",
"updatedAt": "2021-06-02T18:20:29.310Z"
}

通常来说,新增文件的 createdAt 是 ORM 自动生成的当前时间,updateAt 也是自动生成的。当我们传递的额外参数通过了校验,这是非常危险的,所幸 Nestjs 为我们提供了白名单的机制,只需要在初始化 ValidationPipe 的时候加上 ** whitelist:true ** 就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// 新增 whitelist: true
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(3000);
}
bootstrap();

现在 ValidationPipe 将自动删除所有非白名单属性,也就是没有添加验证装饰器的额外参数都会被删除。

使用 ParseIntPipe 转换动态URL路径参数

目前我们的 api 接口中有很多利用到了路径参数,例如 GET /articels/:id, 路径参数获取到是 string 类型,我们需要转为 number 类型,通过 id 查询数据库。

1
2
3
4
5
6
7
// src/articles/articles.controller.ts

@Delete(':id')
@ApiOkResponse({ type: ArticleEntity })
remove(@Param('id') id: string) { // id 被解析成 string 类型
return this.articlesService.remove(+id); // +id 将id转为 number 类型
}

由于id被定义为字符串类型,因此Swagger API在生成的API文档中也将此参数作为字符串记录。这是不直观和不正确的。

使用 Nestjs 内置的 ParseIntPipe 管道可以在路由处理之前,拦截字符串参数并转化为整数,并且修正 Swagger 文档的参数类型。

修改我们的控制器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// src/articles/articles.controller.ts

import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
NotFoundException,
ParseIntPipe,
} from '@nestjs/common';

export class ArticlesController {
// ...

@Get(':id')
@ApiOkResponse({ type: ArticleEntity })
findOne(@Param('id', ParseIntPipe) id: number) {
return this.articlesService.findOne(id);
}

@Patch(':id')
@ApiCreatedResponse({ type: ArticleEntity })
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateArticleDto: UpdateArticleDto,
) {
return this.articlesService.update(id, updateArticleDto);
}

@Delete(':id')
@ApiOkResponse({ type: ArticleEntity })
remove(@Param('id', ParseIntPipe) id: number) {
return this.articlesService.remove(id);
}
}

刷新 Swagger 接口文档,id 参数修改成了 number 类型。

通过文章的学习,我们完成了以下功能:

  • 集成 ValidationPipe 来验证参数类型
  • 去除不必要的额外参数
  • 使用 ParseIntPipe 将 string 转化成 number 类型