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 添加了一些非常棒的 IntelliSense 和语法高亮。
  • ... 可选访问 Unix shell(例如 Linux 和 macOS 中的终端/shell)以运行本系列中提供的命令。

如果您没有 Unix shell(例如,您在 Windows 机器上),您仍然可以学习,但可能需要为您的机器修改 shell 命令。

克隆存储库

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

本教程的起点位于end-rest-api-part-1 分支的 GitHub 存储库中。要开始使用,请克隆存储库并检出 end-rest-api-part-1 分支

现在,执行以下操作以开始使用

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

注意:步骤 4 还会生成 Prisma 客户端并为数据库播种。

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

项目结构和文件

您克隆的存储库应具有以下结构

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

  • src 目录包含应用程序的源代码。有三个模块
    • app 模块位于 src 目录的根目录,是应用程序的入口点。它负责启动 Web 服务器。
    • prisma 模块包含 Prisma 客户端,您的数据库接口。
    • articles 模块定义了 /articles 路由和随附业务逻辑的端点。
  • prisma 模块具有以下内容
    • schema.prisma 文件定义了数据库模式。
    • 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 服务器的终端窗口,您应该看到以下错误

从日志中您可以看到,由于 title 字段(在 Prisma 模式中标记为 @unique),Prisma 客户端抛出了唯一约束验证错误。异常类型为 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。您可以使用它们来处理应用程序中的任何类型的错误。

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

不要错过下一篇文章!

注册 Prisma 新闻通讯