如何使用Prisma和PostgreSQL构建RESTAPI
作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。
介绍
Prisma 是 Node.js 和 TypeScript 的开源 ORM。 它由三个主要工具组成:
- Prisma Client:自动生成和类型安全的查询构建器。
- Prisma Migrate:一个强大的数据建模和迁移系统。
- Prisma Studio:用于查看和编辑数据库中数据的 GUI。
注意: Prisma Migrate 目前处于 Preview 中。 您可以通过 Prisma 路线图 关注开发。
这些工具旨在提高应用程序开发人员在其数据库工作流程中的生产力。 Prisma 的最大好处之一是它提供的抽象级别:应用程序开发人员可以在使用 Prisma 时以更直观的方式推理他们的数据,而不是计算复杂的 SQL 查询或模式迁移。
在本教程中,您将使用 Prisma 和 PostgreSQL 数据库在 TypeScript 中为小型博客应用程序构建 REST API。 您将使用 Docker 在本地设置 PostgreSQL 数据库,并使用 Express 实现 REST API 路由。 在本教程结束时,您将在您的机器上本地运行一个 Web 服务器,它可以响应各种 HTTP 请求并在数据库中读取和写入数据。
先决条件
本教程假定以下内容:
- Node.js v10 或更高版本安装在您的机器上。 您可以使用 如何安装 Node.js 并为您的操作系统创建本地开发环境 指南之一进行设置。
- Docker 安装在你的机器上(运行 PostgreSQL 数据库)。 您可以通过 Docker 网站 在 macOS 和 Windows 上安装,或按照 Linux 发行版的 如何安装和使用 Docker。
对 TypeScript 和 REST API 的基本熟悉很有帮助,但对于本教程来说不是必需的。
第 1 步——创建你的 TypeScript 项目
在这一步中,您将使用 npm
设置一个普通的 TypeScript 项目。 该项目将成为您将在本教程的整个过程中构建的 REST API 的基础。
首先,为您的项目创建一个新目录:
mkdir my-blog
接下来,导航到目录并初始化一个空的 npm
项目。 请注意,此处的 -y
选项意味着您正在跳过命令的交互式提示。 要运行提示,请从命令中删除 -y
:
cd my-blog npm init -y
有关这些提示的更多详细信息,您可以按照 如何使用带有 npm 和 package.json 的 Node.js 模块 中的步骤 1。
您将收到类似于以下内容的输出,其中包含默认响应:
OutputWrote to /.../my-blog/package.json: { "name": "my-blog", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
此命令创建一个最小的 package.json
文件,用作 npm
项目的配置文件。 您现在已准备好在项目中配置 TypeScript。
为简单的 TypeScript 设置执行以下命令:
npm install typescript ts-node @types/node --save-dev
这会在您的项目中安装三个包作为开发依赖项:
- typescript:TypeScript 工具链。
- ts-node:无需事先编译为 JavaScript 即可运行 TypeScript 应用程序的包。
- @types/node:Node.js 的 TypeScript 类型定义。
最后要做的是添加一个 tsconfig.json 文件,以确保为您要构建的应用程序正确配置了 TypeScript。
首先,运行以下命令来创建文件:
nano tsconfig.json
将以下 JSON 代码添加到文件中:
我的博客/tsconfig.json
{ "compilerOptions": { "sourceMap": true, "outDir": "dist", "strict": true, "lib": ["esnext"], "esModuleInterop": true } }
保存并退出文件。
这是 TypeScript 项目的标准和最小配置。 如果您想了解配置文件的各个属性,可以在 TypeScript 文档 中查找它们。
你已经使用 npm
设置了你的普通 TypeScript 项目。 接下来,您将使用 Docker 设置 PostgreSQL 数据库并将 Prisma 连接到它。
第 2 步 — 使用 PostgreSQL 设置 Prisma
在这一步中,您将安装 Prisma CLI,创建初始 Prisma 模式 文件,并使用 Docker 设置 PostgreSQL 并将 Prisma 连接到它。 Prisma 架构是您的 Prisma 设置的主要配置文件,包含您的数据库架构。
首先使用以下命令安装 Prisma CLI:
npm install @prisma/cli --save-dev
作为最佳实践,建议在您的项目中 本地安装 Prisma CLI (而不是全局安装)。 如果您的机器上有多个 Prisma 项目,这有助于避免版本冲突。
接下来,您将使用 Docker 设置 PostgreSQL 数据库。 使用以下命令创建一个新的 Docker Compose 文件:
nano docker-compose.yml
现在将以下代码添加到新创建的文件中:
我的博客/docker-compose.yml
version: '3.8' services: postgres: image: postgres:10.3 restart: always environment: - POSTGRES_USER=sammy - POSTGRES_PASSWORD=your_password volumes: - postgres:/var/lib/postgresql/data ports: - '5432:5432' volumes: postgres:
这个 Docker Compose 文件配置了一个 PostgreSQL 数据库,可以通过 Docker 容器的端口 5432
访问。 另请注意,数据库凭据当前设置为 sammy
(用户)和 your_password
(密码)。 随意将这些凭据调整为您的首选用户和密码。 保存并退出文件。
完成此设置后,继续使用以下命令启动 PostgreSQL 数据库服务器:
docker-compose up -d
此命令的输出将与此类似:
OutputPulling postgres (postgres:10.3)... 10.3: Pulling from library/postgres f2aa67a397c4: Pull complete 6de83ca23e55: Pull complete . . . Status: Downloaded newer image for postgres:10.3 Creating my-blog_postgres_1 ... done
您可以使用以下命令验证数据库服务器是否正在运行:
docker ps
这将输出与此类似的内容:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8547f8e007ba postgres:10.3 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp my-blog_postgres_1
随着数据库服务器的运行,您现在可以创建您的 Prisma 设置。 从 Prisma CLI 运行以下命令:
npx prisma init
这将打印以下输出:
Output✔ Your Prisma schema was created at prisma/schema.prisma. You can now open it in your favorite editor.
请注意,作为最佳实践,您应该在 Prisma CLI 的所有调用前加上 npx
。 这可确保使用您的本地安装。
运行命令后,Prisma CLI 在您的项目中创建了一个名为 prisma
的新文件夹。 它包含以下两个文件:
schema.prisma
:Prisma 项目的主要配置文件(将包括您的数据模型)。.env
:一个 dotenv 文件,用于定义您的数据库连接 URL。
要确保 Prisma 知道数据库的位置,请打开 .env
文件并调整 DATABASE_URL
环境变量。
首先打开.env
文件:
nano prisma/.env
现在您可以按如下方式设置环境变量:
我的博客/prisma/.env
DATABASE_URL="postgresql://sammy:your_password@localhost:5432/my-blog?schema=public"
确保将数据库凭据更改为您在 Docker Compose 文件中指定的凭据。 要了解有关连接 URL 格式的更多信息,请访问 Prisma 文档。
完成后,保存并退出文件。
在这一步中,您使用 Docker 设置 PostgreSQL 数据库,安装 Prisma CLI,并通过环境变量将 Prisma 连接到数据库。 在下一部分中,您将定义数据模型并创建数据库表。
第三步——定义你的数据模型和创建数据库表
在这一步中,您将在 Prisma 模式文件中定义您的 数据模型。 然后,该数据模型将使用 Prisma Migrate 映射到数据库,这将生成并发送 SQL 语句以创建与您的数据模型对应的表。 由于您正在构建博客应用程序,因此应用程序的主要实体将是 users 和 posts。
Prisma 使用自己的 数据建模语言 来定义应用程序数据的形状。
首先,使用以下命令打开您的 schema.prisma
文件:
nano prisma/schema.prisma
现在,向其中添加以下模型定义。 您可以将模型放在文件底部,紧跟在 generator client
块之后:
我的博客/prisma/schema.prisma
. . . model User { id Int @default(autoincrement()) @id email String @unique name String? posts Post[] } model Post { id Int @default(autoincrement()) @id title String content String? published Boolean @default(false) author User? @relation(fields: [authorId], references: [id]) authorId Int? }
保存并退出文件。
您正在定义两个 模型 ,称为 User
和 Post
。 每一个都有许多 字段 代表模型的属性。 模型将映射到数据库表; 这些字段代表各个列。
另请注意,两个模型之间存在一对多 关系,由 User
和 Post
。 这意味着一个用户可以与许多帖子相关联。
有了这些模型,您现在可以使用 Prisma Migrate 在数据库中创建相应的表。 在您的终端中运行以下命令:
npx prisma migrate dev --name "init" --preview-feature
此命令在您的文件系统上创建一个新的 SQL 迁移并将其发送到数据库。 以下是提供给命令的两个选项的快速概述:
--name "init"
:指定迁移的名称(将用于命名在文件系统上创建的迁移文件夹)。--preview-feature
:必需,因为 Prisma Migrate 当前处于 Preview 中。
此命令的输出将与此类似:
OutputEnvironment variables loaded from .env Prisma schema loaded from prisma/schema.prisma Datasource "db": PostgreSQL database "my-blog", schema "public" at "localhost:5432" PostgreSQL database my-blog created at localhost:5432 The following migration(s) have been created and applied from new schema changes: migrations/ └─ 20201209084626_init/ └─ migration.sql Running generate... (Use --skip-generate to skip the generators) ✔ Generated Prisma Client (2.13.0) to ./node_modules/@prisma/client in 75ms
prisma/migrations/20201209084626_init/migration.sql
目录中的 SQL 迁移文件包含对数据库执行的以下语句:
-- CreateTable CREATE TABLE "User" ( "id" SERIAL, "email" TEXT NOT NULL, "name" TEXT, PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Post" ( "id" SERIAL, "title" TEXT NOT NULL, "content" TEXT, "published" BOOLEAN NOT NULL DEFAULT false, "authorId" INTEGER, PRIMARY KEY ("id") ); -- CreateIndex CREATE UNIQUE INDEX "User.email_unique" ON "User"("email"); -- AddForeignKey ALTER TABLE "Post" ADD FOREIGN KEY("authorId")REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
请注意,如果在 prisma migrate dev
命令中添加 --create-only
选项,也可以自定义生成的 SQL 迁移文件,例如设置触发器或使用底层数据库的其他功能。
在这一步中,您在 Prisma 模式中定义了数据模型,并使用 Prisma Migrate 创建了相应的数据库表。 在下一步中,您将在项目中安装 Prisma 客户端,以便您可以查询数据库。
第 4 步 — 在纯脚本中探索 Prisma 客户端查询
Prisma Client 是一个自动生成且类型安全的查询构建器,您可以使用它以编程方式从 Node.js 或 TypeScript 应用程序读取和写入数据库中的数据。 您将使用它在您的 REST API 路由中访问数据库,替换传统的 ORM、普通 SQL 查询、自定义数据访问层或任何其他与数据库通信的方法。
在此步骤中,您将安装 Prisma Client 并熟悉可以使用它发送的查询。 在接下来的步骤中为您的 REST API 实现路由之前,您将首先在一个简单的可执行脚本中探索一些 Prisma Client 查询。
首先,通过打开终端并安装 Prisma Client npm
包,继续在项目中安装 Prisma Client:
npm install @prisma/client
接下来,创建一个名为 src
的新目录,其中将包含您的源文件:
mkdir src
现在在新目录中创建一个 TypeScript 文件:
nano src/index.ts
所有 Prisma 客户端查询都返回 promises,您可以在代码中使用 await
。 这要求您在 async
函数内发送查询。
添加以下样板,其中包含在您的脚本中执行的 async
函数:
我的博客/src/index.ts
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { // ... your Prisma Client queries will go here } main() .catch((e) => console.error(e)) .finally(async () => await prisma.disconnect())
以下是样板的快速细分:
- 从之前安装的
@prisma/client
npm
包中导入PrismaClient
构造函数。 - 您通过调用构造函数来实例化
PrismaClient
并获得一个名为prisma
的实例。 - 您定义了一个名为
main
的async
函数,接下来您将在其中添加 Prisma Client 查询。 - 您调用
main
函数,同时捕获任何潜在异常并确保 Prisma Client 通过调用prisma.disconnect()
关闭任何打开的数据库连接。
使用 main
函数,您可以开始将 Prisma Client 查询添加到脚本中。 调整 index.ts
如下所示:
我的博客/src/index.ts
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { const newUser = await prisma.user.create({ data: { name: 'Alice', email: 'alice@prisma.io', posts: { create: { title: 'Hello World', }, }, }, }) console.log('Created new user: ', newUser) const allUsers = await prisma.user.findMany({ include: { posts: true }, }) console.log('All users: ') console.dir(allUsers, { depth: null }) } main() .catch((e) => console.error(e)) .finally(async () => await prisma.disconnect())
在此代码中,您使用了两个 Prisma Client 查询:
create
:创建一个新的User
记录。 请注意,您实际上使用的是 嵌套写入 ,这意味着您在同一查询中同时创建了User
和Post
记录。findMany
:从数据库中读取所有现有的User
记录。 您正在提供 include 选项,该选项额外加载每个User
记录的相关Post
记录。
现在使用以下命令运行脚本:
npx ts-node src/index.ts
您将在终端中收到以下输出:
OutputCreated new user: { id: 1, email: 'alice@prisma.io', name: 'Alice' } [ { id: 1, email: 'alice@prisma.io', name: 'Alice', posts: [ { id: 1, title: 'Hello World', content: null, published: false, authorId: 1 } ] }
注意: 如果您使用数据库 GUI,您可以通过查看 User
和 Post
表来验证数据是否已创建。 或者,您可以通过运行 npx prisma studio
在 Prisma Studio 中探索数据。
您现在已经使用 Prisma Client 在数据库中读取和写入数据。 在其余步骤中,您将应用这些新知识来实现示例 REST API 的路由。
第 5 步 — 实现您的第一个 REST API 路由
在这一步中,您将在您的应用程序中安装 Express。 Express 是适用于 Node.js 的流行 Web 框架,您将使用它在此项目中实现 REST API 路由。 您将实现的第一个路由将允许您使用 GET
请求从 API 获取所有用户。 将使用 Prisma Client 从数据库中检索用户数据。
继续并使用以下命令安装 Express:
npm install express
由于您使用的是 TypeScript,因此您还需要安装相应的类型作为开发依赖项。 运行以下命令来执行此操作:
npm install @types/express --save-dev
有了适当的依赖关系,您就可以设置您的 Express 应用程序。
首先再次打开您的主源文件:
nano src/index.ts
现在删除 index.ts
中的所有代码并将其替换为以下代码以启动您的 REST API:
我的博客/src/index.ts
import { PrismaClient } from '@prisma/client' import express from 'express' const prisma = new PrismaClient() const app = express() app.use(express.json()) // ... your REST API routes will go here app.listen(3000, () => console.log('REST API server ready at: http://localhost:3000'), )
以下是代码的快速细分:
- 从各自的
npm
包中导入PrismaClient
和express
。 - 您通过调用构造函数来实例化
PrismaClient
并获得一个名为prisma
的实例。 - 您可以通过调用
express()
创建您的 Express 应用程序。 - 您添加
express.json()
中间件以确保 Express 可以正确处理 JSON 数据。 - 您在端口
3000
上启动服务器。
现在您可以实现您的第一条路线。 在对 app.use
和 app.listen
的调用之间,添加以下代码:
我的博客/src/index.ts
. . . app.use(express.json()) app.get('/users', async (req, res) => { const users = await prisma.user.findMany() res.json(users) }) app.listen(3000, () => console.log('REST API server ready at: http://localhost:3000'), )
添加后,保存并退出您的文件。 然后使用以下命令启动本地 Web 服务器:
npx ts-node src/index.ts
您将收到以下输出:
OutputREST API server ready at: http://localhost:3000
要访问 /users
路由,您可以将浏览器指向 http://localhost:3000/users 或任何其他 HTTP 客户端。
在本教程中,您将使用基于终端的 HTTP 客户端 curl 测试所有 REST API 路由。
注意: 如果您更喜欢使用基于 GUI 的 HTTP 客户端,您可以使用 Postwoman 或 Advanced REST Client 等替代方案。
要测试您的路线,请打开一个新的终端窗口或选项卡(以便您的本地 Web 服务器可以继续运行)并执行以下命令:
curl http://localhost:3000/users
您将收到您在上一步中创建的 User
数据:
Output[{"id":1,"email":"alice@prisma.io","name":"Alice"}]
请注意,这次不包括 posts
数组。 这是因为在 /users
路由的实现中,您没有将 include
选项传递给 findMany
调用。
您已经在 /users
实现了您的第一个 REST API 路由。 在下一步中,您将实现剩余的 REST API 路由以向您的 API 添加更多功能。
第 6 步 — 实现剩余的 REST API 路由
在此步骤中,您将为博客应用程序实现剩余的 REST API 路由。 最后,您的 Web 服务器将处理各种 GET
、POST
、PUT
和 DELETE
请求。
以下是您将实施的不同路线的概述:
HTTP 方法 | 路线 | 描述 |
---|---|---|
GET
|
/feed
|
获取所有 发布的 帖子。 |
GET
|
/post/:id
|
通过 ID 获取特定帖子。 |
POST
|
/user
|
创建一个新用户。 |
POST
|
/post
|
创建一个新帖子(作为草稿)。 |
PUT
|
/post/publish/:id
|
将帖子的 published 字段设置为 true 。
|
DELETE
|
post/:id
|
按 ID 删除帖子。 |
继续并首先实现剩余的 GET
路线。
使用以下命令打开 index.ts
:
nano src/index.ts
接下来,在 /users
路由的实现之后添加以下代码:
我的博客/src/index.ts
. . . app.get('/feed', async (req, res) => { const posts = await prisma.post.findMany({ where: { published: true }, include: { author: true } }) res.json(posts) }) app.get(`/post/:id`, async (req, res) => { const { id } = req.params const post = await prisma.post.findOne({ where: { id: Number(id) }, }) res.json(post) }) app.listen(3000, () => console.log('REST API server ready at: http://localhost:3000'), )
保存并退出您的文件。
此代码实现了两个 GET
请求的 API 路由:
/feed
:返回已发布帖子的列表。/post/:id
:按 ID 返回特定帖子。
Prisma Client 用于两种实现。 在 /feed
路由实现中,您使用 Prisma Client 发送的查询过滤所有 Post
记录,其中 published
列包含值 true
。 此外,Prisma 客户端查询使用 include
还为每个返回的帖子获取相关的 author
信息。 在 /post/:id
路由实现中,您传递从 URL 路径检索到的 ID,以便从数据库中读取特定的 Post
记录。
您可以停止服务器在键盘上点击 CTRL+C
。 然后,使用以下命令重新启动服务器:
npx ts-node src/index.ts
要测试 /feed
路由,可以使用以下 curl
命令:
curl http://localhost:3000/feed
由于尚未发布任何帖子,因此响应是一个空数组:
Output[]
要测试 /post/:id
路由,可以使用以下 curl
命令:
curl http://localhost:3000/post/1
这将返回您最初创建的帖子:
Output{"id":1,"title":"Hello World","content":null,"published":false,"authorId":1}
接下来,实现两个 POST
路由。 在三个 GET
路由的实现之后,将以下代码添加到 index.ts
:
我的博客/src/index.ts
. . . app.post(`/user`, async (req, res) => { const result = await prisma.user.create({ data: { ...req.body }, }) res.json(result) }) app.post(`/post`, async (req, res) => { const { title, content, authorEmail } = req.body const result = await prisma.post.create({ data: { title, content, published: false, author: { connect: { email: authorEmail } }, }, }) res.json(result) }) app.listen(3000, () => console.log('REST API server ready at: http://localhost:3000'), )
完成后,保存并退出文件。
此代码实现了两个 POST
请求的 API 路由:
/user
:在数据库中创建一个新用户。/post
:在数据库中创建一个新帖子。
和以前一样,Prisma Client 用于两种实现。 在 /user
路由实现中,您将 HTTP 请求正文中的值传递给 Prisma Client create
查询。
/post
路由有点复杂:这里不能直接从 HTTP 请求的正文中传入值; 相反,您首先需要手动提取它们以将它们传递给 Prisma Client 查询。 原因是请求正文中 JSON 的结构与 Prisma Client 预期的结构不匹配,因此您需要手动创建预期的结构。
您可以通过使用 CTRL+C
停止服务器来测试新路由。 然后,使用以下命令重新启动服务器:
npx ts-node src/index.ts
要通过 /user
路由创建新用户,您可以使用 curl
发送以下 POST
请求:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob", "email":"bob@prisma.io"}' http://localhost:3000/user
这将在数据库中创建一个新用户,打印以下输出:
Output{"id":2,"email":"bob@prisma.io","name":"Bob"}
要通过 /post
路由创建新帖子,您可以使用 curl
发送以下 POST
请求:
curl -X POST -H "Content-Type: application/json" -d '{"title":"I am Bob", "authorEmail":"bob@prisma.io"}' http://localhost:3000/post
这将在数据库中创建一个新帖子,并使用电子邮件 bob@prisma.io
将其连接到用户。 它打印以下输出:
Output{"id":2,"title":"I am Bob","content":null,"published":false,"authorId":2}
最后,您可以实现 PUT
和 DELETE
路由。
使用以下命令打开 index.ts
:
nano src/index.ts
接下来,在两个 POST
路由的实现之后,添加突出显示的代码:
我的博客/src/index.ts
. . . app.put('/post/publish/:id', async (req, res) => { const { id } = req.params const post = await prisma.post.update({ where: { id: Number(id) }, data: { published: true }, }) res.json(post) }) app.delete(`/post/:id`, async (req, res) => { const { id } = req.params const post = await prisma.post.delete({ where: { id: Number(id) }, }) res.json(post) }) app.listen(3000, () => console.log('REST API server ready at: http://localhost:3000'), )
保存并退出您的文件。
此代码为一个 PUT
和一个 DELETE
请求实现 API 路由:
/post/publish/:id
(PUT
):按 ID 发布帖子。/post/:id
(DELETE
):按 ID 删除帖子。
同样,Prisma Client 用于两种实现。 在 /post/publish/:id
路由实现中,要发布的帖子的 ID 从 URL 中检索并传递给 Prisma Client 的 update
查询。 删除数据库中帖子的 /post/:id
路由的实现也从 URL 中检索帖子 ID 并将其传递给 Prisma 客户端的 delete
查询。
再次,用键盘上的 CTRL+C
停止服务器。 然后,使用以下命令重新启动服务器:
npx ts-node src/index.ts
您可以使用以下 curl
命令测试 PUT
路由:
curl -X PUT http://localhost:3000/post/publish/2
这将发布 ID 值为 2
的帖子。 如果您重新发送 /feed
请求,此帖子现在将包含在响应中。
最后,您可以使用以下 curl
命令测试 DELETE
路由:
curl -X DELETE http://localhost:3000/post/1
这将删除 ID 值为 1
的帖子。 要验证该 ID 的帖子是否已被删除,您可以向 /post/1
路由重新发送 GET
请求。
在此步骤中,您为博客应用程序实现了剩余的 REST API 路由。 API 现在响应各种 GET
、POST
、PUT
和 DELETE
请求并实现在数据库中读取和写入数据的功能。
结论
在本文中,您创建了一个 REST API 服务器,其中包含许多不同的路由,用于为示例博客应用程序创建、读取、更新和删除用户和发布数据。 在 API 路由内部,您正在使用 Prisma Client 将相应的查询发送到您的数据库。
作为下一步,您可以使用 Prisma Migrate 实现其他 API 路由或扩展您的数据库架构。 请务必访问 Prisma 文档 以了解 Prisma 的不同方面并探索 prisma-examples 存储库中的一些可立即运行的示例项目——使用 等工具GraphQL 或 grPC APIs。