歡迎來到本系列教學的第三篇,關於使用 NestJS、Prisma 和 PostgreSQL 建構 REST API!在本教學中,您將學習如何在 NestJS 應用程式中執行錯誤處理。
目錄
簡介
在本系列教學的第一章中,您建立了一個新的 NestJS 專案,並將其與 Prisma、PostgreSQL 和 Swagger 整合。然後,您為部落格應用程式的後端建構了一個基本的 REST API。在第二章中,您學習了如何進行輸入驗證與轉換。
在本章中,您將學習如何在 NestJS 中處理錯誤。您將了解兩種不同的策略
- 首先,您將學習如何在 API 控制器內的應用程式碼中直接偵測並拋出錯誤。
- 接下來,您將學習如何使用例外篩選器來處理應用程式中未處理的例外。
在本教學中,您將使用在第一章中建構的 REST API。您不需要完成第二章即可繼續本教學。
開發環境
若要繼續本教學,您需要:
- ... 已安裝 Node.js。
- ... 已安裝 Docker 和 Docker 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
分支
現在,執行以下動作以開始:
- 導航至複製的目錄
- 安裝依賴項
- 使用 Docker 啟動 PostgreSQL 資料庫
- 套用資料庫遷移
- 啟動專案
注意:步驟 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
請求
若要修正此問題,您必須變更 articles.controller.ts
中的 findOne
方法。如果文章不存在,您將拋出 NotFoundException
,這是 NestJS 提供的內建例外。
更新 articles.controller.ts
中的 findOne
方法
如果您再次發出相同的請求,您應該會收到使用者友善的錯誤訊息
使用例外篩選器處理例外
專用例外層的優點
您在前一節中偵測到錯誤狀態,並手動拋出了例外。在許多情況下,例外將會由您的應用程式碼自動產生。在這種情況下,您應該處理例外並向使用者返回適當的 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
方法實作
在這裡,您做了以下變更:
- 為了確保此篩選器捕獲
PrismaClientKnownRequestError
類型的例外,您將其新增至@Catch
裝飾器。 - 例外篩選器擴展了 NestJS 核心套件中的
BaseExceptionFilter
類別。此類別為catch
方法提供了預設實作,該方法會向使用者返回 "Internal server error" 回應。您可以在 NestJS 文件中了解更多關於此內容的資訊。 - 您新增了一個
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/:id
和 DELETE /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 電子報