本文是关于使用 TypeScript、PostgreSQL 和 Prisma 构建后端系列直播和文章的一部分。本文总结了第一次直播,我们将探讨如何使用 Prisma 设计数据模型、执行 CRUD 操作以及查询聚合。

简介
本系列的目标是通过解决一个具体问题来探索和演示现代后端的不同模式、问题和架构:在线课程的评分系统。 这是一个很好的例子,因为它具有多样化的关系类型,并且足够复杂,可以代表一个真实的用例。
上面提供了直播录像,内容与本文相同。
本系列将涵盖的内容
本系列将重点关注数据库在后端开发各个方面的作用,涵盖:
- 数据建模
- CRUD
- 聚合
- API 层
- 验证
- 测试
- 认证
- 授权
- 与外部 API 集成
- 部署
今天您将学到什么
本系列的第一篇文章将首先阐述问题领域,并开发后端的以下方面
- 数据建模: 将问题领域映射到数据库模式
- CRUD: 使用 Prisma Client 对数据库执行创建、读取、更新和删除查询
- 聚合: 使用 Prisma 实现聚合查询以计算平均值等
到本文结束时,您将拥有一个 Prisma 模式、一个由 Prisma Migrate 创建的相应数据库模式,以及一个使用 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 入门”课程可以有两名教师和十名学生。
- 考试: 一个课程可以有多次考试来评估学生的理解能力。考试有日期,并与课程相关。
- 考试结果: 每次考试可以为每个学生生成多条考试结果记录。此外,考试结果还与批改考试的教师相关。
注意:实体代表物理对象或无形概念。例如,用户代表一个人,而课程是一个无形的概念。
实体可以可视化以演示它们如何在关系数据库(本例中为 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 模式
要在数据库中创建表,您首先需要定义您的Prisma 模式。Prisma 模式是数据库表的声明性配置,将由Prisma Migrate用于在数据库中创建表。与上面的实体图类似,它定义了数据库表之间的列和关系。
Prisma 模式用作生成 Prisma Client 和 Prisma Migrate 创建数据库模式的真相来源。
项目的 Prisma 模式可以在 prisma/schema.prisma 中找到。在该模式中,您将找到在此步骤中定义的 stub 模型和一个 datasource 块。datasource 块定义了您将连接的数据库类型和连接字符串。使用 env("DATABASE_URL"),Prisma 将从环境变量加载数据库连接 URL。
注意: 将秘密信息排除在代码库之外被认为是最佳实践。因此,
env("DATABASE_URL")定义在数据源块中。通过设置环境变量,您可以将秘密信息排除在代码库之外。
定义模型
Prisma 模式的基本构建块是 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。 testResults字段,类型为TestResult[](关系字段)
像 test 和 testResults 这样的关系字段可以通过其值类型指向另一个模型来识别,例如 Test 和 TestResult。它们的名称将影响使用 Prisma Client 以编程方式访问关系的方式,但是,它们不代表真实的数据库列。
多对多关系
在此步骤中,您将定义 User 和 Course 模型之间的多对多关系。
多对多关系在 Prisma 模式中可以是隐式或显式。在本部分中,您将了解两者之间的区别以及何时选择隐式或显式。
首先,考虑上一步中定义的 User 和 Course 模型
要创建隐式多对多关系,请在关系的两侧将关系字段定义为列表
这样,Prisma 将创建关系表,以便评分系统可以维护上面定义的属性
- 一门课程可以有多个关联用户。
- 一个用户可以关联多门课程。
然而,评分系统的要求之一是允许将用户以教师或学生的角色与课程关联。这意味着我们需要一种方法来在数据库中存储有关关系的“元信息”。
这可以通过使用显式多对多关系来实现。User 和 Course 之间的关系表需要一个额外的字段来指示用户是课程的教师还是学生。通过显式多对多关系,您可以在关系表上定义额外的字段。
为此,为名为 CourseEnrollment 的关系表定义一个新模型,并将 User 模型中的 courses 字段和 Course 模型中的 members 字段更新为 CourseEnrollment[] 类型,如下所示
关于 CourseEnrollment 模型的注意事项
- 它使用
UserRole枚举来表示用户是课程的学生还是教师。 @@id[userId, courseId]定义了这两个字段的多字段主键。这将确保每个User只能与一个Course关联一次,要么是学生要么是教师,但不能两者兼是。
要了解更多关于关系的信息,请查看关系文档。
完整模式
现在您已经了解了如何定义关系,请将 Prisma 模式更新为以下内容
请注意,TestResult 与 User 模型有两个关系:student 和 gradedBy,分别表示批改考试的教师和参加考试的学生。当单个模型与同一个模型有多个关系时,@relation 属性上的 name 参数是区分关系所必需的。
迁移数据库
定义好 Prisma 模式后,您现在将使用 Prisma Migrate 在数据库中创建实际的表。
首先,在本地设置 DATABASE_URL 环境变量,以便 Prisma 可以连接到您的数据库。
注意:本地数据库的用户名和密码在
docker-compose.yml中均定义为prisma。
要使用 Prisma Migrate 创建和运行迁移,请在终端中运行以下命令
该命令将执行两项操作
- 保存迁移: Prisma Migrate 将捕获您的模式快照,并找出执行迁移所需的 SQL。包含 SQL 的迁移文件将保存到
prisma/migrations - 运行迁移: Prisma Migrate 将执行迁移文件中的 SQL 以运行迁移并修改(或创建)数据库模式
注意: Prisma Migrate 目前处于预览模式。这意味着不建议在生产环境中使用 Prisma Migrate。
检查点: 您应该在输出中看到类似以下内容
恭喜,您已成功设计数据模型并创建了数据库模式。在下一步中,您将使用 Prisma Client 对数据库执行 CRUD 和聚合查询。
生成 Prisma Client
Prisma Client 是一个根据您的数据库模式量身定制的自动生成数据库客户端。它通过解析 Prisma 模式并生成一个 TypeScript 客户端来工作,您可以在代码中导入该客户端。
生成 Prisma Client 通常需要三个步骤
-
将以下
generator定义添加到您的 Prisma 模式中 -
安装
@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 函数中创建用户,如下所示
此操作将在“用户”表中创建一行并返回已创建的用户(包括已创建的 id)。值得注意的是,user 将推断出在 @prisma/client 中定义的 User 类型。
要执行种子脚本并创建 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 引用)中的新行,该行使用 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 模式、使用 Prisma Migrate 进行数据库迁移、使用 Prisma Client 进行 CRUD 以及聚合。
在深入研究代码之前,通常建议先规划问题领域,因为它会影响数据模型的设计,而数据模型又会影响后端的各个方面。
虽然 Prisma 旨在简化关系数据库的操作,但更深入地理解底层数据库可能会有所帮助。
请查阅 Prisma 的数据指南,了解有关数据库工作原理、如何选择合适的数据库以及如何充分利用数据库与应用程序的更多信息。
在本系列的后续部分中,您将了解更多信息
- API 层
- 验证
- 测试
- 认证
- 授权
- 与外部 API 集成
- 部署
请加入 下一次直播,将于 8 月 12 日欧洲中部夏令时晚上 6:00 在 YouTube 上直播。
不要错过下一篇文章!
订阅 Prisma 新闻通讯