2022 年 6 月 3 日

使用 NestJS 和 Prisma 建構 REST API

20 分鐘閱讀

NestJS 是著名的 Node.js 框架之一,最近獲得了許多開發人員的喜愛和關注。本文將教您如何使用 NestJS、Prisma、PostgreSQL 和 Swagger 建構後端 REST API。

Building a REST API with NestJS and Prisma

目錄

簡介

在本教學中,您將學習如何為名為「Median」的部落格應用程式(一個簡單的 Medium 克隆)建構後端 REST API。您將從建立新的 NestJS 專案開始。然後,您將啟動自己的 PostgreSQL 伺服器,並使用 Prisma 連接到它。最後,您將建構 REST API 並使用 Swagger 文件化它。

The final application

您將使用的技術

您將使用以下工具來建構此應用程式

先決條件

假設知識

這是一個適合初學者的教學。但是,本教學假設

  • JavaScript 或 TypeScript 的基本知識(較佳)
  • NestJS 的基本知識

注意:如果您不熟悉 NestJS,您可以透過閱讀 NestJS 文件中的概述章節快速學習基礎知識。

開發環境

為了跟隨本教學,您需要

注意 1:選用的 Prisma VSCode 擴充套件為 Prisma 新增了一些非常棒的 IntelliSense 和語法突顯功能。

注意 2:如果您沒有 Unix shell(例如,您在 Windows 機器上),您仍然可以跟隨,但 shell 命令可能需要針對您的機器進行修改。

產生 NestJS 專案

您需要做的第一件事是安裝 NestJS CLI。當使用 NestJS 專案時,NestJS CLI 非常方便。它配備了內建實用程式,可協助您初始化、開發和維護 NestJS 應用程式。

您可以使用 NestJS CLI 建立一個空專案。若要開始,請在您要放置專案的位置執行以下命令

CLI 將提示您為您的專案選擇一個套件管理器 — 選擇 npm。之後,您應該在目前目錄中擁有一個新的 NestJS 專案。

在您偏好的程式碼編輯器(我們建議 VSCode)中開啟專案。您應該會看到以下檔案

您將處理的大部分程式碼都將位於 src 目錄中。NestJS CLI 已為您建立了一些檔案。一些值得注意的檔案是

  • src/app.module.ts:應用程式的根模組。
  • src/app.controller.ts:具有單一路線的基本控制器:/。此路線將傳回一個簡單的 'Hello World!' 訊息。
  • src/main.ts:應用程式的進入點。它將啟動 NestJS 應用程式。

您可以使用以下命令啟動您的專案

此命令將監看您的檔案,並在您進行變更時自動重新編譯和重新載入伺服器。若要驗證伺服器是否正在執行,請前往 URL https://127.0.0.1:3000/。您應該會看到一個空白頁面,並顯示訊息 'Hello World!'

注意:在您完成本教學課程時,您應該保持伺服器在背景執行。

建立 PostgreSQL 執行個體

您將使用 PostgreSQL 作為 NestJS 應用程式的資料庫。本教學將向您展示如何透過 Docker 容器在您的機器上安裝和執行 PostgreSQL。

注意:如果您不想使用 Docker,您可以原生設定 PostgreSQL 執行個體,或在 Heroku 上取得託管的 PostgreSQL 資料庫

首先,在您的專案主資料夾中建立一個 docker-compose.yml 檔案

docker-compose.yml 檔案是一個組態檔,其中將包含在內部設定 PostgreSQL 的情況下執行 docker 容器的規格。在檔案內建立以下組態

關於此組態,有幾件事需要了解

  • image 選項定義要使用的 Docker 映像。在這裡,您使用的是 postgres 映像 13.5 版。
  • environment 選項指定在初始化期間傳遞給容器的環境變數。您可以定義組態選項和密碼(例如使用者名稱和密碼)— 容器將在此處使用。
  • volumes 選項用於在主機檔案系統中持久化資料。
  • ports 選項將埠從主機機器對應到容器。格式遵循 'host_port:container_port' 慣例。在此情況下,您將主機機器的埠 5432 對應到 postgres 容器的埠 54325432 慣例上是 PostgreSQL 使用的埠。

請確定您的機器埠 5432 上沒有任何執行中的程式。若要啟動 postgres 容器,請開啟新的終端機視窗,並在您的專案主資料夾中執行以下命令

如果一切運作正常,新的終端機視窗應該會顯示資料庫系統已準備好接受連線的記錄。您應該會在終端機視窗內看到類似以下的記錄

恭喜 🎉。您現在擁有自己的 PostgreSQL 資料庫可以盡情使用了!

注意:如果您關閉終端機視窗,它也會停止容器。如果您在命令結尾新增 -d 選項,即可避免這種情況,例如:docker-compose up -d。這將在背景無限期地執行容器。

設定 Prisma

現在資料庫已準備就緒,是時候設定 Prisma 了!

初始化 Prisma

若要開始,請先安裝 Prisma CLI 作為開發相依性。Prisma CLI 可讓您執行各種命令並與您的專案互動。

您可以透過執行以下命令在專案內初始化 Prisma

這將建立一個新的 prisma 目錄,其中包含 schema.prisma 檔案。這是包含您的資料庫 Schema 的主要組態檔。此命令也會在您的專案內建立一個 .env 檔案。

設定您的環境變數

.env 檔案內,您應該會看到一個 DATABASE_URL 環境變數,其中包含虛擬連線字串。將此連線字串取代為您的 PostgreSQL 執行個體的連線字串。

注意:如果您未使用 docker(如上一節所示)來建立您的 PostgreSQL 資料庫,則您的連線字串會與上面顯示的不同。PostgreSQL 的連線字串格式可在Prisma 文件中找到。

了解 Prisma Schema

如果您開啟 prisma/schema.prisma,您應該會看到以下預設 Schema

此檔案是以Prisma Schema 語言撰寫,Prisma 使用此語言來定義您的資料庫 Schema。schema.prisma 檔案有三個主要元件

  • 資料來源:指定您的資料庫連線。以上組態表示您的資料庫提供者是 PostgreSQL,而資料庫連線字串可在 DATABASE_URL 環境變數中找到。
  • 產生器:指出您想要為您的資料庫產生 Prisma Client,這是一個型別安全的查詢建構器。它用於將查詢傳送至您的資料庫。
  • 資料模型:定義您的資料庫模型。每個模型都會對應到基礎資料庫中的表格。目前您的 Schema 中沒有模型,您將在下一節中探索此部分。

注意:如需 Prisma Schema 的詳細資訊,請查看Prisma 文件

資料建模

現在是時候為您的應用程式定義資料模型了。對於本教學,您只需要一個 Article 模型來表示部落格上的每篇文章。

prisma/prisma.schema 檔案內,將一個名為 Article 的新模型新增至您的 Schema

在這裡,您已建立一個包含數個欄位的 Article 模型。每個欄位都有一個名稱(idtitle 等)、一個類型(IntString 等)和其他選用屬性(@id@unique 等)。您可以透過在欄位類型後新增 ? 使欄位成為選用欄位。

id 欄位有一個名為 @id 的特殊屬性。此屬性表示此欄位是模型的主鍵。@default(autoincrement()) 屬性表示此欄位應自動遞增並指派給任何新建立的記錄。

published 欄位是一個旗標,用於指示文章是否已發佈或處於草稿模式。@default(false) 屬性表示此欄位預設應設定為 false

兩個 DateTime 欄位 createdAtupdatedAt 將追蹤文章的建立時間和上次更新時間。@updatedAt 屬性將在每次修改文章時自動使用目前的時間戳記更新欄位。

遷移資料庫

定義 Prisma Schema 後,您將執行遷移以在資料庫中建立實際的表格。若要產生和執行您的第一個遷移,請在終端機中執行以下命令

此命令將執行三件事

  1. 儲存遷移:Prisma Migrate 將取得您的 Schema 快照,並找出執行遷移所需的 SQL 命令。Prisma 會將包含 SQL 命令的遷移檔案儲存到新建立的 prisma/migrations 資料夾。
  2. 執行遷移:Prisma Migrate 將執行遷移檔案中的 SQL,以在您的資料庫中建立基礎表格。
  3. 產生 Prisma Client:Prisma 將根據您的最新 Schema 產生 Prisma Client。由於您未安裝 Client 程式庫,CLI 也會為您安裝。您應該會在 package.json 檔案中的 dependencies 內看到 @prisma/client 套件。Prisma Client 是一個 TypeScript 查詢建構器,會從您的 Prisma Schema 自動產生。它是針對您的 Prisma Schema 量身打造,並將用於將查詢傳送至資料庫。

注意:您可以在Prisma 文件中深入了解 Prisma Migrate。

如果成功完成,您應該會看到如下訊息

檢查產生的遷移檔案,以了解 Prisma Migrate 在幕後執行的動作

注意:您的遷移檔案名稱會稍有不同。

這是建立 PostgreSQL 資料庫中 Article 表格所需的 SQL。它是根據您的 Prisma Schema 由 Prisma 自動產生和執行的。

種子資料庫

目前,資料庫是空的。因此,您將建立一個種子指令碼,以使用一些虛擬資料來填入資料庫。

首先,建立一個名為 prisma/seed.ts 的種子檔案。此檔案將包含虛擬資料和種子資料庫所需的查詢。

然後,在種子檔案內,新增以下程式碼

在此指令碼內,您首先初始化 Prisma Client。然後,您使用 prisma.upsert() 函數建立兩篇文章。upsert 函數只會在沒有文章符合 where 條件時建立新文章。您正在使用 upsert 查詢而不是 create 查詢,因為 upsert 會移除與意外嘗試插入相同記錄兩次相關的錯誤。

您需要告訴 Prisma 在執行種子命令時要執行的指令碼。您可以透過將 prisma.seed 鍵新增至 package.json 檔案結尾來執行此操作

seed 命令將執行您先前定義的 prisma/seed.ts 指令碼。此命令應自動運作,因為 ts-node 已安裝為 package.json 中的開發相依性。

使用以下命令執行種子

您應該會看到以下輸出

注意:您可以在Prisma 文件中深入了解種子。

建立 Prisma 服務

在您的 NestJS 應用程式中,將 Prisma Client API 從您的應用程式抽象化出來是一種良好的做法。若要執行此操作,您將建立一個新的服務,其中將包含 Prisma Client。此服務名為 PrismaService,將負責實例化 PrismaClient 執行個體並連線到您的資料庫。

Nest CLI 提供了一種簡單的方法,可以直接從 CLI 產生模組和服務。在您的終端機中執行以下命令

注意 1:如有必要,請參閱 NestJS 文件以取得服務模組的簡介。

注意 2:在某些情況下,在伺服器已在執行的情況下執行 nest generate 命令可能會導致 NestJS 擲回例外狀況,指出:Error: Cannot find module './app.controller'。如果您遇到此錯誤,請從終端機執行以下命令:rm -rf dist 並重新啟動伺服器。

這應該會產生一個新的子目錄 ./src/prisma,其中包含 prisma.module.tsprisma.service.ts 檔案。服務檔案應包含以下程式碼

Prisma 模組將負責建立 PrismaServicesingleton 執行個體,並允許在您的應用程式中共用服務。若要執行此操作,您會將 PrismaService 新增至 prisma.module.ts 檔案中的 exports 陣列

現在,任何匯入 PrismaModule 的模組都將有權存取 PrismaService,並且可以將其注入到自己的元件/服務中。這是 NestJS 應用程式的常見模式。

完成此設定後,您就完成了 Prisma 的設定!您現在可以開始建構 REST API。

設定 Swagger

Swagger 是一個使用 OpenAPI 規格來文件化您的 API 的工具。Nest 有一個專用於 Swagger 的模組,您將很快使用它。

首先安裝所需的相依性

現在開啟 main.ts 並使用 SwaggerModule 類別初始化 Swagger

當應用程式執行時,開啟您的瀏覽器並導覽至 https://127.0.0.1:3000/api。您應該會看到 Swagger UI。

Swagger User Interface

Article 模型實作 CRUD 操作

在本節中,您將為 Article 模型實作建立、讀取、更新和刪除 (CRUD) 操作,以及任何隨附的商業邏輯。

產生 REST 資源

在您可以實作 REST API 之前,您需要為 Article 模型產生 REST 資源。這可以使用 Nest CLI 快速完成。在您的終端機中執行以下命令

系統會提供您一些 CLI 提示。相應地回答問題

  1. 您想要為此資源使用什麼名稱(複數,例如「users」)? articles
  2. 您使用什麼傳輸層? REST API
  3. 您想要產生 CRUD 進入點嗎?

您現在應該會看到一個新的 src/articles 目錄,其中包含 REST 終端節點的所有樣板。在 src/articles/articles.controller.ts 檔案中,您會看到不同路由(也稱為路由處理器)的定義。處理每個請求的業務邏輯封裝在 src/articles/articles.service.ts 檔案中。目前,此檔案包含虛擬實作。

如果您再次開啟 Swagger API 頁面,您應該會看到類似這樣的內容

Auto-generated "articles" endpoints

SwaggerModule 會搜尋路由處理器上的所有 @Body()@Query()@Param() 裝飾器,以產生此 API 頁面。

PrismaClient 新增至 Articles 模組

若要在 Articles 模組內存取 PrismaClient,您必須將 PrismaModule 新增為匯入項目。將以下 imports 新增至 ArticlesModule

您現在可以在 ArticlesService 內注入 PrismaService,並使用它來存取資料庫。若要執行此操作,請將建構子新增至 articles.service.ts,如下所示

定義 GET /articles 終端節點

此終端節點的控制器稱為 findAll。此終端節點將傳回資料庫中所有已發佈的文章。findAll 控制器看起來像這樣

您需要更新 ArticlesService.findAll() 以傳回資料庫中所有已發佈文章的陣列

findMany 查詢將傳回所有符合 where 條件的 article 記錄。

您可以前往 https://127.0.0.1:3000/api 並按一下 GET/articles 下拉式選單來測試終端節點。按下 Try it out,然後按下 Execute 以查看結果。

注意:您也可以直接在瀏覽器中或透過 REST 用戶端(例如 Postman)執行所有請求。如果您想在終端機中執行 HTTP 請求,Swagger 也會為每個請求產生 curl 命令。

定義 GET /articles/drafts 終端節點

您將定義一個新的路由來擷取所有未發佈的文章。NestJS 沒有自動為此終端節點產生控制器路由處理器,因此您必須自行撰寫。

您的編輯器應顯示錯誤,指出沒有名為 articlesService.findDrafts() 的函式。若要修正此問題,請在 ArticlesService 中實作 findDrafts 方法

GET /articles/drafts 終端節點現在將在 Swagger API 頁面中提供。

注意:我建議您在完成實作後,透過 Swagger API 頁面 測試每個終端節點。

定義 GET /articles/:id 終端節點

此終端節點的控制器路由處理器稱為 findOne。它看起來像這樣

路由接受動態 id 參數,該參數會傳遞至 findOne 控制器路由處理器。由於 Article 模型具有整數 id 欄位,因此需要使用 + 運算子將 id 參數轉換為數字。

現在,更新 ArticlesService 中的 findOne 方法,以傳回具有給定 ID 的文章

再次前往 https://127.0.0.1:3000/api 來測試終端節點。按一下 GET /articles/{id} 下拉式選單。按下 Try it out,將有效值新增至 id 參數,然後按下 Execute 以查看結果。

定義 POST /articles 終端節點

這是用於建立新文章的終端節點。此終端節點的控制器路由處理器稱為 create。它看起來像這樣

請注意,它預期請求 body 中有 CreateArticleDto 類型的引數。DTO(資料傳輸物件)是一個物件,用於定義資料將如何在網路上傳送。CreateArticleDto 目前是一個空類別。您將在其中新增屬性以定義請求 body 的形狀。

需要 @ApiProperty 裝飾器才能讓類別屬性對 SwaggerModule 可見。如需更多資訊,請參閱 NestJS 文件

CreateArticleDto 現在應在 Swagger API 頁面的 Schemas 下定義。UpdateArticleDto 的形狀是從 CreateArticleDto 定義自動推斷而來。因此 UpdateArticleDto 也會在 Swagger 內部定義。

現在更新 ArticlesService 中的 create 方法,以在資料庫中建立新文章

定義 PATCH /articles/:id 終端節點

此終端節點用於更新現有文章。此終端節點的路由處理器稱為 update。它看起來像這樣

updateArticleDto 定義為 CreateArticleDtoPartialType。因此,它可以具有 CreateArticleDto 的所有屬性。

就像之前一樣,您必須更新此操作的對應服務方法

article.update 操作將嘗試尋找具有給定 idArticle 記錄,並使用 updateArticleDto 的資料更新它。

如果在資料庫中找不到這類 Article 記錄,Prisma 將傳回錯誤。在這種情況下,API 不會傳回使用者友善的錯誤訊息。您將在未來的教學課程中瞭解如何使用 NestJS 處理錯誤。

定義 DELETE /articles/:id 終端節點

此終端節點用於刪除現有文章。此終端節點的路由處理器稱為 remove。它看起來像這樣

就像之前一樣,前往 ArticlesService 並更新對應的方法

這是 articles 終端節點的最後一個操作。恭喜,您的 API 幾乎準備就緒! 🎉

在 Swagger 中將終端節點群組在一起

@ApiTags 裝飾器新增至 ArticlesController 類別,以在 Swagger 中將所有 articles 終端節點群組在一起

API 頁面現在已將 articles 終端節點群組在一起。

更新 Swagger 回應類型

如果您查看 Swagger 中每個終端節點下的 Responses 標籤,您會發現 Description 是空的。這是因為 Swagger 不知道任何終端節點的回應類型。您將使用一些裝飾器來修正此問題。

首先,您需要定義一個實體,Swagger 可以使用它來識別傳回的 entity 物件的形狀。若要執行此操作,請如下所示更新 articles.entity.ts 檔案中的 ArticleEntity 類別

這是由 Prisma Client 產生的 Article 類型的實作,其中為每個屬性新增了 @ApiProperty 裝飾器。

現在,是時候使用正確的回應類型註釋控制器路由處理器了。NestJS 有一組用於此目的的裝飾器。

您為 GETPATCHDELETE 終端節點新增了 @ApiOkResponse,並為 POST 終端節點新增了 @ApiCreatedResponsetype 屬性用於指定傳回類型。您可以在 NestJS 文件中找到 NestJS 提供的所有回應裝飾器。

現在,Swagger 應在 API 頁面上正確定義所有終端節點的回應類型。

摘要與結論

恭喜!您已使用 NestJS 建置了一個基本的 REST API。在本教學課程中,您

  • 使用 NestJS 建置了 REST API
  • 在 NestJS 專案中順利整合了 Prisma
  • 使用 Swagger 和 OpenAPI 記錄了您的 REST API

本教學課程的主要重點之一是如何輕鬆使用 NestJS 和 Prisma 建置 REST API。對於快速建置結構良好、類型安全且可維護的後端應用程式而言,這是一個非常有效率的堆疊。

您可以在 GitHub 上找到此專案的原始碼。如果您發現問題,請隨時在儲存庫中提出問題或提交 PR。您也可以直接在 Twitter 上與我聯繫。

請勿錯過下一篇文章!

註冊 Prisma 電子報