2022 年 12 月 14 日

使用 NestJS 和 Prisma 构建 REST API:错误处理

阅读时间 6 分钟

欢迎来到本系列的第三个教程,本系列是关于使用 NestJS、Prisma 和 PostgreSQL 构建 REST API 的!在本教程中,您将学习如何在 NestJS 应用程序中执行错误处理。

Building a REST API with NestJS and Prisma: Error Handling

目录

简介

在本系列第一章中,您创建了一个新的 NestJS 项目,并将其与 Prisma、PostgreSQL 和 Swagger 集成。然后,您为博客应用程序的后端构建了一个基本的 REST API。在第二章中,您学习了如何进行输入验证和转换。

在本章中,您将学习如何在 NestJS 中处理错误。您将研究两种不同的策略

  1. 首先,您将学习如何在 API 控制器中直接检测和抛出应用程序代码中的错误。
  2. 接下来,您将学习如何使用异常过滤器来处理整个应用程序中未处理的异常。

在本教程中,您将使用第一章中构建的 REST API。您不需要完成第二章即可跟随本教程。

开发环境

为了完成本教程,您需要

  • ... 已安装 Node.js
  • ... 已安装 DockerDocker Compose。如果您使用的是 Linux,请确保您的 Docker 版本为 20.10.0 或更高。您可以通过在终端中运行 docker version 来检查您的 Docker 版本。
  • ... _可选_安装 Prisma VS Code 扩展。Prisma VS Code 扩展为 Prisma 添加了一些非常好的智能感知和语法高亮。
  • ... _可选_访问 Unix shell(例如 Linux 和 macOS 中的终端/shell)来运行本系列中提供的命令。

如果您没有 Unix shell(例如,您使用的是 Windows 计算机),您仍然可以继续学习,但 shell 命令可能需要根据您的计算机进行修改。

克隆存储库

本教程的起点是本系列第一部分的结尾。它包含一个使用 NestJS 构建的基本 REST API。

本教程的起点可在GitHub 存储库end-rest-api-part-1分支中找到。要开始,请克隆存储库并检出end-rest-api-part-1分支

现在,执行以下操作以开始:

  1. 导航到克隆的目录
  1. 安装依赖项
  1. 使用 Docker 启动 PostgreSQL 数据库
  1. 应用数据库迁移
  1. 启动项目

注意:步骤 4 还会生成 Prisma Client 并为数据库填充数据。

现在,您应该能够通过 https://:3000/api/ 访问 API 文档。

项目结构和文件

您克隆的仓库应该具有以下结构:

此存储库中值得注意的文件和目录是

  • src 目录包含应用程序的源代码。有三个模块:
    • app 模块位于 src 目录的根目录,是应用程序的入口点。它负责启动 Web 服务器。
    • prisma 模块包含 Prisma Client,它是您与数据库的接口。
    • articles 模块定义了 /articles 路由的端点和相应的业务逻辑。
  • prisma 模块具有以下内容
    • schema.prisma 文件定义了数据库 schema。
    • migrations 目录包含数据库迁移历史记录。
    • seed.ts 文件包含一个用于使用虚拟数据填充开发数据库的脚本。
  • docker-compose.yml 文件定义了您的 PostgreSQL 数据库的 Docker 镜像。
  • .env 文件包含您的 PostgreSQL 数据库的数据库连接字符串。

注意:有关这些组件的更多信息,请参阅本教程系列的第一部分

直接检测和抛出异常

本节将教您如何在应用程序代码中直接抛出异常。您将解决 GET /articles/:id 端点中的一个问题。目前,如果您为此端点提供一个不存在的 id 值,它将返回空,并带有 HTTP 200 状态,而不是错误。

例如,尝试发出 GET /articles/234235 请求

Requesting an article that does not exist returns HTTP 200

为了解决这个问题,您必须更改 articles.controller.ts 中的 findOne 方法。如果文章不存在,您将抛出 NotFoundException,这是 NestJS 提供的内置异常。

更新 articles.controller.ts 中的 findOne 方法

如果您再次发出相同的请求,您应该会收到一条用户友好的错误消息

Requesting an article that does not exist returns HTTP 404

使用异常过滤器处理异常

专用异常层的优点

您在上一节中检测到错误状态并手动抛出了异常。在许多情况下,应用程序代码会自动生成异常。在这种情况下,您应该处理异常并向用户返回适当的 HTTP 错误。

虽然可以在每个控制器中手动处理异常,但由于以下多种原因,这不是一个好主意

  • 它会将您的核心应用程序逻辑与大量错误处理代码混淆。
  • 您的许多端点将处理相似的错误,例如找不到资源。您必须在许多地方复制相同的错误处理代码。
  • 由于您的错误处理逻辑分散在许多位置,因此很难更改。

为了解决这些问题,NestJS 有一个异常层,它负责处理应用程序中未处理的异常。在 NestJS 中,您可以创建 _异常过滤器_,它们定义如何处理应用程序中抛出的不同类型的异常。

NestJS 全局异常过滤器

NestJS 有一个全局异常过滤器,它捕获所有未处理的异常。为了理解全局异常过滤器,让我们看一个示例。向 POST /articles 端点发送_两个_请求,其正文如下

第一个请求将成功,但第二个请求将失败,因为您已经创建了一个具有相同 title 字段的文章。您将收到以下错误

如果您查看运行 NestJS 服务器的终端窗口,您应该会看到以下错误

从日志中可以看出,Prisma Client 由于 title 字段抛出了唯一的约束验证错误,该字段在 Prisma 模式中被标记为 @unique。异常类型为 PrismaClientKnownRequestError,并在 Prisma 命名空间级别导出。

由于 PrismaClientKnownRequestError 未被您的应用程序直接处理,因此它由内置的全局异常过滤器自动处理。此过滤器生成 HTTP 500 “内部服务器错误”响应。

创建手动异常过滤器

在本节中,您将创建一个自定义异常过滤器来处理您看到的 PrismaClientKnownRequestError。此过滤器将捕获所有类型为 PrismaClientKnownRequestError 的异常,并向用户返回清晰的用户友好错误消息。

首先使用 Nest CLI 生成一个过滤器类

这将创建一个新文件 src/prisma-client-exception.filter.ts,内容如下

注意:还创建了第二个文件 src/prisma-client-exception.filter.spec.ts 用于创建测试。您现在可以忽略此文件。

您将收到来自 eslint 的错误,因为 catch 方法为空。如下更新 PrismaClientExceptionFilter 中的 catch 方法实现

您已进行以下更改

  1. 为确保此过滤器捕获类型为 PrismaClientKnownRequestError 的异常,您已将其添加到 @Catch 装饰器中。
  2. 异常过滤器扩展了 NestJS 核心包中的 BaseExceptionFilter 类。该类为 catch 方法提供了一个默认实现,它向用户返回“内部服务器错误”响应。您可以在NestJS 文档中了解更多信息。
  3. 您添加了一个 console.error 语句,将错误消息记录到控制台。这对于调试很有用。

Prisma 会针对许多不同类型的错误抛出 PrismaClientKnownRequestError。因此,您需要弄清楚如何从 PrismaClientKnownRequestError 异常中提取错误代码。PrismaClientKnownRequestError 异常具有一个包含错误代码的 code 属性。您可以在Prisma 错误消息参考中找到错误代码列表。

您正在寻找的错误代码是 P2002,它发生在唯一约束冲突时。现在您将更新 catch 方法,以便在此错误发生时抛出 HTTP 409 Conflict 响应。您还将向用户提供自定义错误消息。

像这样更新您的异常过滤器实现

在这里,您正在访问底层框架的 Response 对象并直接修改响应。默认情况下,Express 是 NestJS 底层使用的 HTTP 框架。对于 P2002 以外的任何异常代码,您都发送默认的“内部服务器错误”响应。

注意:对于生产应用程序,请注意不要在错误消息中向用户泄露任何敏感信息。

将异常过滤器应用于您的应用程序

现在,为了使 PrismaClientExceptionFilter 生效,您需要将其应用于特定范围。异常过滤器可以作用于单个路由(方法范围)、整个控制器(控制器范围)或整个应用程序(全局范围)。

通过更新 main.ts 文件将异常过滤器应用于您的整个应用程序

现在,尝试向 POST /articles 端点发出相同的请求

这次您将收到一条更友好的错误消息

由于 PrismaClientExceptionFilter 是一个全局过滤器,它可以处理应用程序中所有路由的这种特定类型的错误。

我建议扩展异常过滤器实现以处理其他错误。例如,您可以添加一个案例来处理 P2025 错误代码,该代码在数据库中找不到记录时发生。您应该为此错误返回状态码 HttpStatus.NOT_FOUND。这对于 PATCH /articles/:idDELETE /articles/:id 端点将很有用。

额外奖励:使用 nestjs-prisma 包处理 Prisma 异常

到目前为止,您已经学习了在 NestJS 应用程序中_手动_处理 Prisma 异常的不同技术。有一个专门用于将 Prisma 与 NestJS 结合使用的包,名为nestjs-prisma,您也可以使用它来处理 Prisma 异常。此包是一个值得考虑的优秀选项,因为它消除了大量样板代码。

有关安装和使用此包的说明可在 nestjs-prisma 文档中找到。使用此包时,您无需手动创建单独的 prisma 模块和服务,因为此包将自动为您创建它们。

您可以在文档的异常过滤器部分了解如何使用该包处理 Prisma 异常。在本教程的未来章节中,我们将更详细地介绍 nestjs-prisma 包。

总结和最后说明

恭喜!在本教程中,您使用了一个现有的 NestJS 应用程序,并学习了如何集成错误处理。您学习了两种不同的错误处理方式:直接在应用程序代码中处理和通过创建异常过滤器处理。

在本章中,您学习了如何处理 Prisma 错误。但这些技术本身并不仅限于 Prisma。您可以使用它们来处理应用程序中的任何类型的错误。

您可以在GitHub 存储库end-error-handling-part-3分支中找到本教程的完整代码。如果您发现问题,请随时在存储库中提出问题或提交 PR。您也可以直接在Twitter上联系我。

不要错过下一篇文章!

订阅 Prisma 新闻通讯

© . This site is unofficial and not affiliated with Prisma Data, Inc.