本文是關於使用TypeScript、PostgreSQL和Prisma構建後端系列直播和文章的一部分。在本文中,我們將回顧如何設計資料模型、執行CRUD操作以及使用Prisma查詢聚合,本文總結了第一次直播內容。

引言
本系列的目標是透過解決一個具體問題——線上課程評分系統,探索並展示現代後端的不同模式、問題和架構。這是一個很好的例子,因為它具有多樣化的關係型別,並且足夠複雜,足以代表現實世界的用例。
上述提供了直播錄影,其內容與本文相同。
本系列將涵蓋的內容
本系列將聚焦資料庫在後端開發各個方面的作用,包括:
- 資料建模
- CRUD
- 聚合
- API層
- 驗證
- 測試
- 身份驗證
- 授權
- 與外部API整合
- 部署
今日您將學習到的內容
本系列的第一篇文章將首先闡述問題領域,然後開發後端的以下方面:
- 資料建模:將問題領域對映到資料庫schema
- CRUD:使用Prisma Client實現針對資料庫的建立、讀取、更新和刪除查詢
- 聚合:使用Prisma實現聚合查詢,以計算平均值等。
在本文結束時,您將擁有一個Prisma schema、一個由Prisma Migrate建立的相應資料庫schema,以及一個使用Prisma Client執行CRUD和聚合查詢的種子指令碼。
本系列的後續部分將詳細介紹列表中的其他方面。
注意:在整個指南中,您會發現各種檢查點,它們能幫助您驗證是否正確執行了步驟。
先決條件
假定知識
本系列假定您具備TypeScript、Node.js和關係型資料庫的基礎知識。如果您熟悉JavaScript但尚未嘗試TypeScript,您仍然應該能夠跟上。本系列將使用PostgreSQL,但大多數概念也適用於其他關係型資料庫,如MySQL。除此之外,無需具備Prisma的先驗知識,因為本系列將對此進行介紹。
開發環境
您應該安裝以下軟體:
如果您使用Visual Studio Code,建議安裝Prisma擴充套件,以實現語法高亮、格式化和其他輔助功能。
注意:如果您不想使用Docker,可以設定一個本地PostgreSQL資料庫或在Heroku上設定一個託管的PostgreSQL資料庫。
克隆倉庫
本系列的原始碼可在GitHub上找到。
要開始,請克隆此倉庫並安裝依賴項:
注意:透過檢出
part-1分支,您將能夠從相同的起始點開始閱讀本文。
啟動PostgreSQL
要啟動PostgreSQL,請在real-world-grading-app資料夾中執行以下命令:
注意: Docker將使用
docker-compose.yml檔案來啟動PostgreSQL容器。
線上課程評分系統的資料模型
定義問題領域和實體
在構建後端時,首要關注的問題之一是對問題領域的正確理解。問題領域(或問題空間)是指定義問題並約束解決方案(約束是問題的一部分)的所有資訊。透過理解問題領域,資料模型的形態和結構應該會變得清晰。
線上評分系統將包含以下實體:
- 使用者:擁有賬戶的人員。使用者可以透過其與課程的關係,成為教師或學生。換句話說,同一使用者既可以是一門課程的教師,也可以是另一門課程的學生。
- 課程:一門學習課程,包含一名或多名教師和學生,以及一次或多次測試。例如:“TypeScript入門”課程可以有兩名教師和十名學生。
- 測試:一門課程可以有多次測試,以評估學生的理解程度。測試具有日期,並與課程相關聯。
- 測試結果:每次測試可以為每個學生生成多條測試結果記錄。此外,TestResult還與批改測試的教師相關聯。
注意:實體表示物理物件或無形概念。例如,使用者代表人,而課程則是無形概念。
實體可以被視覺化,以展示它們在關係型資料庫(本例中為PostgreSQL)中如何表示。下面的圖表添加了每個實體的相關列和外部索引鍵,以描述實體之間的關係。

關於此圖表首先要注意的是,每個實體都對映到一個數據庫表。
該圖表具有以下關係:
- 一對多(也稱為
1-n):Test↔TestResultCourse↔TestUser↔TestResult(透過graderId)User↔TestResult(透過student)
- 多對多(也稱為
m-n)User↔Course(透過CourseEnrollment關係表,該表包含兩個外部索引鍵:userId和courseId)。多對多關係通常需要一個額外的表。這是必要的,以便評分系統可以具有以下屬性:- 一門課程可以關聯多個使用者(作為學生或教師)
- 一個使用者可以關聯多門課程。
注意:關係表(也稱為JOIN表)連線兩個或多個其他表,以在其間建立關係。建立關係表是SQL中一種常見的資料建模實踐,用於表示不同實體之間的關係。本質上,這意味著“一個m-n關係在資料庫中被建模為兩個1-n關係”。
理解Prisma schema
要在資料庫中建立表,您首先需要定義您的Prisma schema。Prisma schema是資料庫表的宣告性配置,將由Prisma Migrate用於在資料庫中建立表。與上面的實體圖類似,它定義了資料庫表之間的列和關係。
Prisma schema被用作生成Prisma Client和Prisma Migrate以建立資料庫schema的單一事實來源。
該專案的Prisma schema可以在prisma/schema.prisma中找到。在schema中,您將找到在本步驟中將定義的存根模型和一個datasource塊。datasource塊定義了您將連線的資料庫型別和連線字串。透過env("DATABASE_URL"),Prisma將從環境變數中載入資料庫連線URL。
注意:將秘密資訊排除在程式碼庫之外被認為是最佳實踐。因此,
env("DATABASE_URL")定義在datasource塊中。透過設定環境變數,您可以將秘密資訊與程式碼庫分離。
定義模型
Prisma schema的基本組成部分是model。每個模型都對映到一個數據庫表。
以下是顯示模型基本簽名的示例:
在這裡,您定義了一個包含多個欄位的User模型。每個欄位都有一個名稱,後跟型別和可選的欄位屬性。例如,id欄位可以分解如下:
idInt標量-@id(表示主鍵)和@default(autoincrement())(設定預設自增值)emailString標量-@uniquefirstNameString標量--lastNameString標量--socialJson標量?(可選)-Prisma定義了一組資料型別,這些型別根據所使用的資料庫對映到原生資料庫型別。
Json資料型別允許儲存自由格式的JSON。這對於在User記錄之間可能不一致且可以在不影響後端核心功能的情況下更改的資訊非常有用。在上面的User模型中,它將用於儲存社交連結,例如Twitter、LinkedIn等。向social新增新的社交資料連結不需要資料庫遷移。
對問題領域和使用Prisma建模資料有了很好的理解後,您現在可以將以下模型新增到您的prisma/schema.prisma檔案中:
每個模型都包含所有相關欄位,同時忽略關係(關係將在下一步中定義)。
定義關係
一對多
在此步驟中,您將定義Test和TestResult之間的一對多關係。
首先,考慮上一步中定義的Test和TestResult模型:
要在這兩個模型之間定義一對多關係,請新增以下三個欄位:
- 在關係的“多”端,
TestResult模型中的型別為Int的testId欄位(關係標量)。此欄位表示底層資料庫表中的外部索引鍵。 - 型別為
Test的test欄位(關係欄位),帶有@relation屬性,將關係標量testId對映到Test模型的主鍵id。 - 型別為
TestResult[]的testResults欄位(關係欄位)
關係欄位,例如test和testResults,可以透過它們指向另一個模型的值型別來識別,例如Test和TestResult。它們的名稱將影響使用Prisma Client以程式設計方式訪問關係的方式,但它們不代表真實的資料庫列。
多對多關係
在此步驟中,您將定義User和Course模型之間的多對多關係。
多對多關係在Prisma schema中可以是隱式的或顯式的。在本部分中,您將瞭解兩者之間的區別以及何時選擇隱式或顯式。
首先,考慮上一步中定義的User和Course模型:
要建立隱式多對多關係,請在關係的兩側將關係欄位定義為列表:
透過此操作,Prisma將建立關係表,以便評分系統可以維護上述定義的屬性:
- 一門課程可以關聯多個使用者。
- 一個使用者可以關聯多門課程。
然而,評分系統的一個要求是允許使用者以教師或學生的身份關聯到課程。這意味著我們需要一種在資料庫中儲存有關關係的“元資訊”的方式。
這可以透過使用顯式多對多關係來實現。User和Course之間的關係表需要一個額外欄位來指示使用者是課程的教師還是學生。透過顯式多對多關係,您可以在關係表上定義額外欄位。
為此,請為關係表定義一個名為CourseEnrollment的新模型,並將User模型中的courses欄位和Course模型中的members欄位更新為CourseEnrollment[]型別,如下所示:
關於CourseEnrollment模型需要注意的事項:
- 它使用
UserRole列舉來表示使用者是課程的學生還是教師。 @@id[userId, courseId]定義了這兩個欄位的多欄位主鍵。這將確保每個User只能與一個Course關聯一次,無論是作為學生還是作為教師,但不能兩者兼是。
要了解更多關於關係的資訊,請檢視關係文件。
完整schema
現在您已經瞭解瞭如何定義關係,請使用以下內容更新Prisma schema:
請注意,TestResult與User模型有兩個關係:student和gradedBy,分別表示批改測試的教師和參加測試的學生。@relation屬性上的name引數是必要的,以便在單個模型與同一模型存在多個關係時消除關係歧義。
遷移資料庫
定義了Prisma schema後,您現在將使用Prisma Migrate在資料庫中建立實際的表。
首先,在本地設定DATABASE_URL環境變數,以便Prisma可以連線到您的資料庫。
注意:本地資料庫的使用者名稱和密碼在
docker-compose.yml中都定義為prisma。
要使用Prisma Migrate建立並執行遷移,請在終端中執行以下命令:
該命令將執行兩項操作:
- 儲存遷移: Prisma Migrate將獲取您的schema快照,並計算出執行遷移所需的SQL。包含SQL的遷移檔案將儲存到
prisma/migrations中。 - 執行遷移: Prisma Migrate將執行遷移檔案中的SQL,以執行遷移並更改(或建立)資料庫schema。
注意: Prisma Migrate目前處於預覽模式。這意味著不建議在生產環境中使用Prisma Migrate。
檢查點:您應該在輸出中看到類似以下內容:
恭喜您,您已成功設計資料模型並建立了資料庫schema。在下一步中,您將使用Prisma Client對資料庫執行CRUD和聚合查詢。
生成Prisma Client
Prisma Client是一個根據您的資料庫schema自動生成的資料庫客戶端。它透過解析Prisma schema並生成一個TypeScript客戶端,您可以在程式碼中匯入該客戶端。
生成Prisma Client通常需要三個步驟:
-
將以下
generator定義新增到您的Prisma schema中: -
安裝
@prisma/clientnpm包 -
使用以下命令生成Prisma Client:
檢查點:您應該在輸出中看到以下內容:✔ Generated Prisma Client to ./node_modules/@prisma/client in 57ms
填充資料庫
在此步驟中,您將使用Prisma Client編寫一個種子指令碼,以向資料庫填充一些示例資料。
在此上下文中,種子指令碼是一系列使用Prisma Client執行的CRUD操作(建立、讀取、更新和刪除)。您還將使用巢狀寫入,以便在單個操作中為相關實體建立資料庫行。
開啟骨架檔案src/seed.ts,您將在其中找到已匯入的Prisma Client和兩個Prisma Client函式呼叫:一個用於例項化Prisma Client,另一個用於在指令碼執行完成後斷開連線。
建立使用者
首先在main函式中按以下方式建立使用者:
此操作將在User表中建立一個行並返回建立的使用者(包括建立的id)。值得注意的是,user將推斷出User型別,該型別在@prisma/client中定義。
要執行種子指令碼並建立User記錄,您可以使用package.json中的seed指令碼,如下所示:
在接下來的步驟中,您將多次執行種子指令碼。為了避免出現唯一約束錯誤,您可以在main函式的開頭刪除資料庫內容,如下所示:
注意:這些命令會刪除每個資料庫表中的所有行。請謹慎使用,並在生產環境中避免此操作!
建立課程以及相關的測試和使用者
在此步驟中,您將建立一個課程,並使用巢狀寫入來建立相關的測試。
將以下內容新增到main函式中:
這將在Course表中建立一個行,並在Tests表中建立三個相關行(Course和Tests之間存在一對多關係,這使得此操作成為可能)。
如果您想將上一步中建立的使用者作為教師與此課程建立關係,該怎麼辦?
User和Course具有顯式的多對多關係。這意味著我們必須在CourseEnrollment表中建立行,並分配一個角色,以將User連結到Course。
可以按以下方式完成(在前一步的查詢基礎上新增):
注意:
include引數允許您在結果中獲取關係。這在後續步驟中將測試結果與測試關聯時會很有用。
使用巢狀寫入(如members和tests)時,有兩種選擇:
connect:與現有行建立關係create:建立新行並建立關係
對於tests,您傳遞了一個物件陣列,這些物件連結到已建立的課程。
對於members,同時使用了create和connect。這是必要的,因為即使user已經存在,也需要在一個關係表(由members引用的CourseEnrollment)中建立一條新行,該行使用connect與先前建立的使用者形成關係。
建立使用者並與課程關聯
在上一步中,您建立了一個課程、相關測試併為課程分配了一名教師。在此步驟中,您將建立更多使用者並將其作為學生與課程關聯。
新增以下語句:
為學生新增測試結果
檢視TestResult模型,它有三個關係:student、gradedBy和test。要為Shakuntala和David新增測試結果,您將像之前的步驟一樣使用巢狀寫入。
以下是TestResult模型的再次引用:
新增單個測試結果將如下所示:
要為David和Shakuntala分別新增三次測試的測試結果,您可以建立一個迴圈:
恭喜您,如果您已達到此步,您已成功在資料庫中為使用者、課程、測試和測試結果建立了示例資料。
要探索資料庫中的資料,您可以執行Prisma Studio。Prisma Studio是資料庫的視覺化編輯器。要執行Prisma Studio,請在終端中執行以下命令:
使用Prisma Client聚合測試結果
Prisma Client允許您對模型的數字欄位(例如Int和Float)執行聚合操作。聚合操作從一組輸入值(即表中的多行)中計算出單個結果。例如,計算一組TestResult行中result列的最小值、最大值和平均值。
在此步驟中,您將執行兩種聚合操作:
-
針對課程中每個測試,統計所有學生的聚合結果,以表示測試的難度或班級對測試主題的理解程度。
這會產生以下結果:
-
針對每個學生,統計所有測試的聚合結果,以表示學生在課程中的表現。
這會產生以下終端輸出:
總結與後續步驟
本文涵蓋了廣泛的內容,從問題領域開始,深入探討了資料建模、Prisma Schema、使用Prisma Migrate進行資料庫遷移、使用Prisma Client進行CRUD操作以及聚合。
在開始編寫程式碼之前,梳理問題領域通常是一個很好的建議,因為它為資料模型的設計提供了依據,而資料模型則影響著後端開發的方方面面。
雖然Prisma旨在簡化關係型資料庫的使用,但對底層資料庫有更深入的瞭解會很有幫助。
請檢視Prisma資料指南,瞭解更多關於資料庫的工作原理、如何選擇合適的資料庫以及如何充分利用資料庫與您的應用程式。
在本系列的後續部分中,您將瞭解更多關於:
- API層
- 驗證
- 測試
- 身份驗證
- 授權
- 與外部API整合
- 部署
歡迎加入下一次直播,該直播將於8月12日中歐夏令時下午6:00在YouTube上進行。
不要錯過下一篇文章!
訂閱Prisma新聞通訊