2022 年 7 月 19 日

使用 NestJS 和 Prisma 建構 REST API:輸入驗證與轉換

8 分鐘閱讀時間

歡迎來到關於使用 NestJS、Prisma 和 PostgreSQL 建構 REST API 系列的第二篇教學!在本教學中,您將學習如何在 API 中執行輸入驗證和轉換。

Building a REST API with NestJS and Prisma: Input Validation & Transformation

目錄

簡介

在本系列的第一部分中,您建立了一個新的 NestJS 專案,並將其與 Prisma、PostgreSQL 和 Swagger 整合。然後,您為部落格應用程式的後端建構了一個基礎的 REST API。

在這部分中,您將學習如何驗證輸入,使其符合您的 API 規範。執行輸入驗證是為了確保只有格式正確的資料才能從客戶端傳遞到您的 API。驗證傳送到 Web 應用程式的任何資料的正確性是最佳實務。這有助於防止格式錯誤的資料和濫用您的 API。

您還將學習如何執行輸入轉換。輸入轉換是一種技術,可讓您攔截並轉換從客戶端傳送的資料,然後再由該請求的路由處理常式處理。這對於將資料轉換為適當的類型、將預設值套用到遺失的欄位、清理輸入等非常有用。

開發環境

若要跟隨本教學,您需要具備

注意:

  1. 選用的 Prisma VS Code 擴充功能為 Prisma 新增了一些不錯的 IntelliSense 和語法突顯功能。

  2. 如果您沒有 Unix shell(例如,您使用的是 Windows 機器),您仍然可以繼續學習,但可能需要為您的機器修改 shell 命令。

複製儲存庫

本教學的起點是本系列第一部分的結尾。它包含一個使用 NestJS 建構的基礎 REST API。我建議您在開始本教學之前先完成第一個教學。

本教學的起點可在 GitHub 儲存庫begin-validation 分支中找到。若要開始,請複製儲存庫並檢出 begin-validation 分支

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

  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 資料庫的資料庫連線字串。

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

執行輸入驗證

若要執行輸入驗證,您將使用 NestJS Pipes。Pipes 會對路由處理常式正在處理的引數進行操作。Nest 會在路由處理常式之前調用 pipe,而 pipe 接收傳送給路由處理常式的引數。Pipes 可以執行許多操作,例如驗證輸入、將欄位新增至輸入等等。Pipes 類似於 middleware,但 pipes 的範圍僅限於處理輸入引數。NestJS 提供了一些開箱即用的 pipes,但您也可以建立自己的自訂 pipes

Pipes 有兩個典型的用例

  • 驗證:評估輸入資料,如果有效,則保持不變地傳遞;否則,當資料不正確時,拋出例外。
  • 轉換:將輸入資料轉換為所需的格式(例如,從字串到整數)。

NestJS 驗證 pipe 將檢查傳遞給路由的引數。如果引數有效,pipe 會將引數傳遞給路由處理常式,而無需進行任何修改。但是,如果引數違反任何指定的驗證規則,pipe 將拋出例外。

以下兩個圖表顯示驗證 pipe 如何運作,針對任意 /example 路由。

在本節中,您將重點關注驗證用例。

全域設定 ValidationPipe

若要執行輸入驗證,您將使用內建的 NestJS ValidationPipeValidationPipe 提供了一種方便的方法來為所有傳入的客戶端酬載強制執行驗證規則,其中驗證規則是使用來自 class-validator 套件的裝飾器宣告的。

若要使用此功能,您需要將兩個套件新增至您的專案

class-validator 套件提供用於驗證輸入資料的裝飾器,而 class-transformer 套件提供用於將輸入資料轉換為所需格式的裝飾器。這兩個套件都與 NestJS pipes 良好整合。

現在將 ValidationPipe 匯入到您的 main.ts 檔案中,並使用 app.useGlobalPipes 方法使其在您的應用程式中全域可用

CreateArticleDto 新增驗證規則

您現在將使用 class-validator 套件將驗證裝飾器新增至 CreateArticleDto。您將對 CreateArticleDto 應用以下規則

  1. title 不得為空或短於 5 個字元。
  2. description 的最大長度必須為 300。
  3. bodydescription 不得為空。
  4. titledescriptionbody 必須為 string 類型,而 published 必須為 boolean 類型。

開啟 src/articles/dto/create-article.dto.ts 檔案,並將其內容替換為以下內容

這些規則將被 ValidationPipe 擷取,並自動應用於您的路由處理常式。使用裝飾器進行驗證的優點之一是,CreateArticleDto 仍然是 POST /articles 端點所有引數的單一事實來源。因此,您不需要定義單獨的驗證類別。

測試您已設定的驗證規則。嘗試使用 POST /articles 端點建立文章,並使用非常短的佔位符 title,例如這樣

您應該會收到 HTTP 400 錯誤回應,以及回應本文中關於哪些驗證規則被違反的詳細資訊。

HTTP 400 response with descriptive error message

此圖表說明 ValidationPipe 對於 /articles 路由的無效輸入在幕後執行的操作

Input validation flow with ValidationPipe

從客戶端請求中移除不必要的屬性

CreateArticleDTO 定義了需要傳送到 POST /articles 端點以建立新文章的屬性。UpdateArticleDTO 執行相同的操作,但針對 PATCH /articles/{id} 端點。

目前,對於這兩個端點,都可以傳送 DTO 中未定義的其他屬性。這可能會導致無法預見的錯誤或安全性問題。例如,您可以手動將無效的 createdAtupdatedAt 值傳遞到 POST /articles 端點。由於 TypeScript 類型資訊在執行階段不可用,因此您的應用程式將無法識別這些欄位在 DTO 中不可用。

舉例來說,嘗試將以下請求傳送到 POST /articles 端點

透過這種方式,您可以注入無效的值。在這裡,您建立了一篇 updatedAt 值在 createdAt 之前的文章,這沒有意義。

若要防止這種情況發生,您需要從客戶端請求中篩選掉任何不必要的欄位/屬性。幸運的是,NestJS 也為此提供了開箱即用的功能。您只需要在應用程式中初始化 ValidationPipe 時傳遞 whitelist: true 選項。

將此選項設定為 true 後,ValidationPipe 將自動移除所有「非白名單」屬性,其中「非白名單」表示沒有任何驗證裝飾器的屬性。重要的是要注意,即使在 DTO 中定義了此選項,它也會篩選掉所有沒有驗證裝飾器的屬性。

現在,傳遞到請求的任何其他欄位/屬性都將由 NestJS 自動移除,從而防止先前顯示的漏洞。

注意:NestJS ValidationPipe 是高度可配置的。所有可用的配置選項都記錄在 NestJS 文件中。如有必要,您也可以為您的應用程式建立自訂驗證 pipes

使用 ParseIntPipe 轉換動態 URL 路徑

在您的 API 內部,您目前接受 GET /articles/{id}PATCH /articles/{id}DELETE /articles/{id} 端點的 id 參數作為路徑的一部分。NestJS 從 URL 路徑中將 id 參數解析為字串。然後,在傳遞到 ArticlesService 之前,字串會在您的應用程式碼內部轉換為數字。例如,查看 DELETE /articles/{id} 路由處理常式

由於 id 定義為字串類型,因此 Swagger API 也會在產生的 API 文件中將此引數記錄為字串。這是違反直覺且不正確的。

您可以使用 NestJS pipe 自動將 id 轉換為數字,而不是在路由處理常式內部手動執行此轉換。將內建的 ParseIntPipe 新增至這三個端點的控制器路由處理常式

ParseIntPipe 將攔截字串類型的 id 參數,並在將其傳遞到適當的路由處理常式之前自動將其解析為數字。這也具有在 Swagger 內部將 id 參數正確記錄為數字的優點。

總結與最終想法

恭喜!在本教學中,您使用了一個現有的 REST API 並

  • 使用 ValidationPipe 整合了驗證。
  • 從客戶端請求中移除了不必要的屬性。
  • 整合了 ParseIntPipe 以解析 string 路徑變數並將其轉換為 number

您可能已經注意到 NestJS 非常依賴裝飾器。這是一個非常刻意的設計選擇。NestJS 旨在透過大量利用裝飾器來處理各種跨領域問題,從而提高程式碼的可讀性和模組化。因此,控制器和服務方法不需要為了執行驗證、快取、記錄等操作而充斥著樣板程式碼。

您可以在 GitHub 儲存庫的 end-validation 分支中找到本教學的完成程式碼。如果您發現問題,請隨時在儲存庫中提出 issue 或提交 PR。您也可以直接在 Twitter 上與我聯繫。

不要錯過下一篇文章!

訂閱 Prisma 電子報