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 并为数据库填充数据。

现在,您应该可以通过 http://localhost: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 schema 中被标记为 @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 异常的不同技术。有一个专门用于在 NestJS 中使用 Prisma 的包,名为 nestjs-prisma,您也可以使用它来处理 Prisma 异常。这个包是一个值得考虑的优秀选项,因为它消除了大量样板代码。

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

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

总结和最终说明

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

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

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

不要错过下一篇文章!

订阅 Prisma 邮件列表

© . All rights reserved.