欢迎来到使用 NestJS、Prisma 和 PostgreSQL 构建 REST API 系列的第二篇教程!在本教程中,您将学习如何在 API 中执行输入验证和转换。
目录
引言
在本系列的第一部分中,您创建了一个新的 NestJS 项目,并将其与 Prisma、PostgreSQL 和 Swagger 集成。然后,您为博客应用程序的后端构建了一个基本的 REST API。
在本部分中,您将学习如何验证输入,使其符合您的 API 规范。执行输入验证是为了确保只有格式正确的数据从客户端通过您的 API。验证发送到 Web 应用程序的任何数据的正确性是最佳实践。这有助于防止畸形数据和滥用您的 API。
您还将学习如何执行输入转换。输入转换是一种技术,它允许您在请求的数据被路由处理程序处理之前,拦截并转换从客户端发送的数据。这对于将数据转换为适当的类型、为缺失字段应用默认值、清理输入等非常有用。
开发环境
要跟随本教程,您需要具备以下条件:
- 已安装 Node.js。
- 已安装 Docker 或 PostgreSQL。
- 已安装 Prisma VSCode 扩展。(可选)
- 可访问 Unix shell(如 Linux 和 macOS 中的终端/shell)以运行本系列中提供的命令。(可选)
注意:
可选的 Prisma VS Code 扩展为 Prisma 添加了一些不错的智能感知和语法高亮功能。
如果您没有 Unix shell(例如,您使用的是 Windows 机器),您仍然可以继续学习,但可能需要根据您的机器修改 shell 命令。
克隆仓库
本教程的起点是本系列第一部分的结尾。它包含一个用 NestJS 构建的初步 REST API。我建议您在开始本教程之前完成第一个教程。
本教程的起点可在 GitHub 仓库的 begin-validation 分支中找到。要开始,请克隆仓库并检出 begin-validation
分支
现在,执行以下操作以开始:
- 导航到克隆的目录
- 安装依赖
- 使用 Docker 启动 PostgreSQL 数据库
- 应用数据库迁移
- 启动项目
注意:步骤 4 还会生成 Prisma Client 并为数据库填充初始数据。
现在,您应该能够通过 http://localhost:3000/api/
访问 API 文档。
项目结构和文件
您克隆的仓库应具有以下结构:
此仓库中值得注意的文件和目录有:
src
目录包含应用程序的源代码。有三个模块:app
模块位于src
目录的根部,是应用程序的入口点。它负责启动 Web 服务器。prisma
模块包含 Prisma Client,即您的数据库查询构建器。articles
模块定义了/articles
路由的端点及相应的业务逻辑。
prisma
模块包含以下内容:schema.prisma
文件定义了数据库模式。migrations
目录包含数据库迁移历史。seed.ts
文件包含一个脚本,用于向您的开发数据库填充模拟数据。
docker-compose.yml
文件定义了您的 PostgreSQL 数据库的 Docker 镜像。.env
文件包含您的 PostgreSQL 数据库的连接字符串。
注意:有关这些组件的更多信息,请参阅本教程系列的第一部分。
执行输入验证
要执行输入验证,您将使用 NestJS Pipes(管道)。管道作用于路由处理程序正在处理的参数。Nest 在路由处理程序之前调用管道,管道接收路由处理程序所需的参数。管道可以做很多事情,例如验证输入、向输入添加字段等。管道类似于中间件,但管道的范围仅限于处理输入参数。NestJS 提供了一些开箱即用的管道,但您也可以创建自己的自定义管道。
管道有两种典型的用例:
- 验证:评估输入数据,如果有效,则保持不变地传递;否则,当数据不正确时抛出异常。
- 转换:将输入数据转换为所需的形式(例如,从字符串转换为整数)。
NestJS 验证管道将检查传递给路由的参数。如果参数有效,管道将不加修改地将参数传递给路由处理程序。但是,如果参数违反任何指定的验证规则,管道将抛出异常。
以下两张图展示了验证管道对于任意 /example
路由的工作原理。
在本节中,您将重点关注验证用例。
全局设置 ValidationPipe
要执行输入验证,您将使用 NestJS 内置的 ValidationPipe
。 ValidationPipe
提供了一种便捷的方法来强制所有传入客户端有效载荷的验证规则,这些验证规则通过 class-validator
包中的装饰器声明。
要使用此功能,您需要向项目中添加两个包:
class-validator 包提供了用于验证输入数据的装饰器,而 class-transformer 包提供了用于将输入数据转换为所需形式的装饰器。这两个包都与 NestJS 管道良好集成。
现在,在您的 main.ts
文件中导入 ValidationPipe
,并使用 app.useGlobalPipes
方法使其在您的应用程序中全局可用:
为 CreateArticleDto
添加验证规则
现在,您将使用 class-validator
包为 CreateArticleDto
添加验证装饰器。您将为 CreateArticleDto
应用以下规则:
title
不能为空,且长度不能少于 5 个字符。description
的最大长度必须为 300。body
和description
不能为空。title
、description
和body
必须是string
类型,而published
必须是boolean
类型。
打开 src/articles/dto/create-article.dto.ts
文件并将其内容替换为以下内容:
这些规则将被 ValidationPipe
捕获并自动应用于您的路由处理程序。使用装饰器进行验证的优点之一是 CreateArticleDto
仍然是 POST /articles
端点所有参数的单一真相来源。因此您无需定义单独的验证类。
测试您已设置的验证规则。尝试使用 POST /articles
端点创建一个文章,其中 title
非常短,例如这样:
您应该会收到一个 HTTP 400 错误响应,以及响应正文中关于违反了哪些验证规则的详细信息。
此图解释了 ValidationPipe
在幕后如何处理 /articles
路由的无效输入:
从客户端请求中剥离不必要的属性
CreateArticleDTO
定义了需要发送到 POST /articles
端点以创建新文章的属性。UpdateArticleDTO
也做同样的事情,但用于 PATCH /articles/{id}
端点。
目前,对于这两个端点,都可以发送 DTO 中未定义的额外属性。这可能导致不可预见的错误或安全问题。例如,您可以手动将无效的 createdAt
和 updatedAt
值传递给 POST /articles
端点。由于 TypeScript 类型信息在运行时不可用,您的应用程序将无法识别这些字段在 DTO 中不可用。
举个例子,尝试向 POST /articles
端点发送以下请求:
通过这种方式,您可以注入无效值。在这里,您创建了一篇文章,其 updatedAt
值早于 createdAt
,这没有意义。
为了防止这种情况,您需要从客户端请求中过滤掉任何不必要的字段/属性。幸运的是,NestJS 也为此提供了开箱即用的解决方案。您只需在应用程序中初始化 ValidationPipe
时传递 whitelist: true
选项即可。
将此选项设置为 true 后,ValidationPipe
将自动移除所有未列入白名单的属性,其中“未列入白名单”指的是没有任何验证装饰器的属性。需要注意的是,此选项将过滤掉所有没有验证装饰器的属性,即使它们在 DTO 中定义了也是如此。
现在,任何传递给请求的额外字段/属性都将由 NestJS 自动剥离,从而防止了之前展示的漏洞。
注意:NestJS
ValidationPipe
是高度可配置的。所有可用的配置选项都在 NestJS 文档中进行了说明。如有必要,您还可以为您的应用程序构建自定义验证管道。
使用 ParseIntPipe
转换动态 URL 路径
在您的 API 中,您目前接受 GET /articles/{id}
、PATCH /articles/{id}
和 DELETE /articles/{id}
端点的 id 参数作为路径的一部分。NestJS 从 URL 路径中将 id
参数解析为字符串。然后,该字符串在传递给 ArticlesService 之前在您的应用程序代码中被转换为数字。例如,查看 DELETE /articles/{id}
路由处理程序:
由于 id
被定义为字符串类型,Swagger API 也在生成的 API 文档中将此参数记录为字符串。这不直观且不正确。
与其在路由处理程序内部手动进行此转换,不如使用 NestJS 管道自动将 id
转换为数字。将内置的 ParseIntPipe
添加到这三个端点的控制器路由处理程序中:
ParseIntPipe
将拦截字符串类型的 id
参数,并自动将其解析为数字,然后传递给相应的路由处理程序。这还有一个优点,即在 Swagger 中将 id
参数正确地记录为数字。
总结和最终说明
恭喜!在本教程中,您对现有的 REST API 进行了以下操作:
- 使用
ValidationPipe
集成了验证功能。 - 剥离了客户端请求中不必要的属性。
- 集成了
ParseIntPipe
,用于解析string
路径变量并将其转换为number
。
您可能已经注意到 NestJS 大量依赖于装饰器。这是一个非常有意的设计选择。NestJS 旨在通过大量利用装饰器来解决各种横切关注点,从而提高代码可读性和模块化。因此,控制器和服务方法无需被用于验证、缓存、日志记录等样板代码所填充。
您可以在 GitHub 仓库的 end-validation 分支中找到本教程的完整代码。如果您发现问题,请随时在仓库中提出问题或提交 PR。您也可以直接在 Twitter 上联系我。
不要错过下一篇文章!
订阅 Prisma 新闻通讯