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 儲存庫中。若要開始,請複製儲存庫並 checkout end-rest-api-part-1 分支

現在,執行以下動作以開始:

  1. 導航至複製的目錄
  1. 安裝依賴項
  1. 使用 Docker 啟動 PostgreSQL 資料庫
  1. 套用資料庫遷移
  1. 啟動專案

注意:步驟 4 也會產生 Prisma Client 並植入資料庫。

現在,您應該可以透過 https://127.0.0.1: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 資料庫的資料庫連線字串。

注意:如需更多關於這些元件的資訊,請參閱本教學系列的第一部分

直接偵測並拋出例外

本節將教您如何在應用程式碼中直接拋出例外。您將解決 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 "Internal Server Error" 回應。

建立手動例外篩選器

在本節中,您將建立一個自訂例外篩選器,以處理您看到的 PrismaClientKnownRequestError。此篩選器將捕獲所有 PrismaClientKnownRequestError 類型的例外,並向使用者返回清晰且使用者友善的錯誤訊息。

首先,使用 Nest CLI 產生一個篩選器類別

這將建立一個新的檔案 src/prisma-client-exception.filter.ts,內容如下

注意:還有一個建立的第二個檔案 src/prisma-client-exception.filter.spec.ts 用於建立測試。您現在可以忽略此檔案。

由於 catch 方法為空,eslint 會顯示錯誤。如下所示更新 PrismaClientExceptionFilter 中的 catch 方法實作

在這裡,您做了以下變更:

  1. 為了確保此篩選器捕獲 PrismaClientKnownRequestError 類型的例外,您將其新增至 @Catch 裝飾器。
  2. 例外篩選器擴展了 NestJS 核心套件中的 BaseExceptionFilter 類別。此類別為 catch 方法提供了預設實作,該方法會向使用者返回 "Internal server error" 回應。您可以在 NestJS 文件中了解更多關於此內容的資訊。
  3. 您新增了一個 console.error 語句,將錯誤訊息記錄到主控台。這對於除錯很有用。

Prisma 會針對許多不同種類的錯誤拋出 PrismaClientKnownRequestError。因此,您需要弄清楚如何從 PrismaClientKnownRequestError 例外中提取錯誤代碼。PrismaClientKnownRequestError 例外具有一個 code 屬性,其中包含錯誤代碼。您可以在 Prisma 錯誤訊息參考中找到錯誤代碼列表。

您正在尋找的錯誤代碼是 P2002,它發生在唯一約束違規時。您現在將更新 catch 方法,以便在此錯誤發生時拋出 HTTP 409 Conflict 回應。您也將向使用者提供自訂錯誤訊息。

像這樣更新您的例外篩選器實作

在這裡,您正在存取底層框架 Response 物件,並直接修改回應。預設情況下,express 是 NestJS 在底層使用的 HTTP 框架。對於 P2002 以外的任何例外代碼,您都會發送預設的 "Internal server error" 回應。

注意:對於生產應用程式,請注意不要在錯誤訊息中向使用者洩露任何敏感資訊。

將例外篩選器套用至您的應用程式

現在,為了讓 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 儲存庫中找到本教學的完成程式碼。如果您發現問題,請隨時在儲存庫中提出 issue 或提交 PR。您也可以直接在 Twitter 上與我聯繫。

別錯過下一篇文章!

訂閱 Prisma 電子報