使用Prisma与PostgreSQL构建高性能REST API:完整指南
简介
Prisma 是用于 Node.js 和 TypeScript 的开源 ORM(对象关系映射)。它由三个主要工具组成:
- Prisma Client: 一个自动生成且类型安全的查询构建器。
- Prisma Migrate: 一个强大的数据建模和迁移系统。
- Prisma Studio: 一个用于查看和编辑数据库数据的图形用户界面(GUI)。
这些工具旨在提高应用程序开发者在数据库工作流中的生产力。Prisma 的最大优势之一是其提供的抽象层级:应用程序开发者在使用 Prisma 时,无需研究复杂的 SQL 查询或模式迁移,而是可以更直观地思考他们的数据。
在本教程中,您将使用 Prisma 和 PostgreSQL 数据库,在 TypeScript 中为一个小型博客应用构建一个 REST API。您将使用 Docker 在本地设置您的 PostgreSQL 数据库,并使用 Express 实现 REST API 路由。在教程结束时,您将在您的机器上运行一个本地的 Web 服务器,可以响应各种 HTTP 请求并在数据库中读写数据。
先决条件
本教程假设您已满足以下条件:
- 您的机器上已安装 Node.js 14 或更高版本。您可以参考适用于您操作系统的《如何安装 Node.js 并创建本地开发环境》指南进行设置。
- 您的机器上已安装 Docker(用于运行 PostgreSQL 数据库)。您可以通过 Docker 官网在 macOS 和 Windows 上安装,或遵循《如何安装和使用 Docker》指南进行 Linux 发行版安装。
对于本教程而言,对 TypeScript 和 REST API 有一定的基本了解会有帮助,但并非必需。
第一步 – 创建您的 TypeScript 项目
在这一步中,您将使用 npm 建立一个纯 TypeScript 项目。这个项目将成为您在本教程中要构建的 REST API 的基础。
首先,为您的项目创建一个新目录。
mkdir my-blog
然后,进入目录并初始化一个空的 npm 项目。请注意,这里的 -y
选项意味着您将跳过命令的交互提示。如果要运行提示,请从命令中移除 -y
选项。
cd my-blog
npm init -y
有关这些提示的更多详细信息,请按照《如何使用 Node.js 模块与 npm 和 package.json》中的第一步操作。
您将会收到类似下述输出的结果,其中包含默认的回应。
Wrote 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 代码添加到文件中。
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": ["esnext"],
"esModuleInterop": true
}
}
保存并退出文件。
这个设置是一个 TypeScript 项目的标准且最小化的配置。如果你想了解配置文件的各个属性,你可以参考 TypeScript 文档。
你已经使用 npm 搭建了一个普通的 TypeScript 项目。接下来,你将使用 Docker 搭建你的 PostgreSQL 数据库,并将 Prisma 与其连接起来。
第二步 — 使用 PostgreSQL 设置 Prisma
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第2部分(共13部分)。
在这一步中,您将安装Prisma CLI,创建您的初始Prisma模式文件,并使用Docker设置PostgreSQL并将其连接到Prisma。Prisma模式是您的Prisma设置的主要配置文件,包含您的数据库模式。
安装Prisma CLI
从以下命令开始安装Prisma CLI:
npm install prisma --save-dev
建议将Prisma CLI本地安装在项目中(而不是全局安装),这是最佳实践。这样做有助于避免版本冲突,尤其是在您的设备上拥有多个Prisma项目的情况下。
使用Docker设置PostgreSQL
接下来,您将使用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文件配置了一个可以通过Docker容器的5432端口访问的PostgreSQL数据库。目前数据库的凭据设置为sammy
(用户名)和your_password
(密码)。请随意将这些凭据调整为您偏好的用户名和密码。保存并退出文件。
启动PostgreSQL数据库服务器
通过这个设置,使用下面的命令启动PostgreSQL数据库服务器。
docker-compose up -d
这个指令的输出将会与这个相似。
Pulling 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
这个命令将会输出类似于这样的内容。
CONTAINER 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设置。从Prisma CLI运行以下命令。
npx prisma init
这个命令将会打印以下的输出结果:
✔ 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。
配置数据库连接URL
为了确保Prisma了解您的数据库位置,打开.env
文件,并调整DATABASE_URL
环境变量。
首先,打开.env
文件。
nano .env
现在您可以按照以下方式更新环境变量。
我的博客/.env
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第3部分(共13部分)。
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语句来创建与数据模型相对应的表格。由于您正在构建一个博客应用程序,该应用程序的主要实体将是用户和帖子。
Prisma利用自己的数据建模语言来定义您应用数据的结构。
首先,使用以下命令打开您的schema.prisma
文件:
- nano 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
模型中,通过posts
和author
关联字段之间存在一对多的关系。这意味着一个用户可以与多篇帖子相关联。
保存并退出文件。
有了这些模型,您现在可以使用Prisma Migrate在数据库中创建相应的表格。在您的终端中运行以下命令:
- npx prisma migrate dev –name init
这个命令在您的文件系统上创建一个新的SQL迁移,并将其发送到数据库。命令中提供的--name init
选项指定了迁移的名称,并将用于命名在文件系统上创建的迁移文件夹。
这个命令的输出将类似于这样:
Environment 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迁移文件中,对数据库执行了以下语句(文件名中的突出部分可能因您的设置而不同):
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第4部分(共13部分)。
-- 创建表
CREATE TABLE "User" (
"id" SERIAL,
"email" TEXT NOT NULL,
"name" TEXT,
PRIMARY KEY ("id")
);
-- 创建表
CREATE TABLE "Post" (
"id" SERIAL,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN NOT NULL DEFAULT false,
"authorId" INTEGER,
PRIMARY KEY ("id")
);
-- 创建唯一索引
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
-- 添加外键
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 Client,以便可以对数据库进行查询。
第四步 — 在一个简单脚本中探索Prisma客户端查询
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第5部分(共13部分)。
内容片段: 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 Client查询都返回可以在代码中等待的Promise。这要求你将查询发送到一个异步函数中。
在src/index.ts
文件中,使用一个在你的脚本中执行的异步函数,并添加以下样板代码。
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
// ... 你的Prisma Client查询将在这里
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect())
以下是对模板的快速解读:
- 你从先前安装的
@prisma/client
npm 包中导入PrismaClient
的构造函数。 - 通过调用构造函数并获取一个名为
prisma
的实例来实例化PrismaClient
。 - 你定义了一个名为
main
的异步函数,在其中添加你的 Prisma Client 查询。 - 你调用
main
函数,捕获任何潜在的异常,并确保 Prisma Client 使用prisma.$disconnect()
关闭任何打开的数据库连接。
在主函数完成后,你可以开始向脚本中添加Prisma客户端查询。调整index.ts
,包括异步函数中的突出显示的行。
index.ts
文件。这是文章《如何使用Prisma和PostgreSQL构建REST API》的第6部分(共13部分)。
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)记录。你使用嵌套写入查询(nested write query)在同一个查询中同时创建用户(User)和帖子(Post)记录。findMany
: 从数据库中读取所有现有的用户(User)记录。你提供了include
选项,该选项会为每个用户(User)记录额外加载相关的帖子(Post)记录。
保存并关闭文件。
现在使用以下命令运行脚本:开始执行脚本。
- npx ts-node src/index.ts
你将在终端上收到以下输出:
创建新用户: { 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:
我的博客/源代码/主入口.ts
import { PrismaClient } from '@prisma/client'
import express from 'express'
const prisma = new PrismaClient()
const app = express()
app.use(express.json())
// ... 您的REST API路由将在此处添加
app.listen(3000, () =>
console.log('REST API 服务器已在: http://localhost:3000 准备就绪'),
)
以下是代码的简要解释:
- 您从各自的npm包中导入
PrismaClient
和express
。 - 您通过调用构造函数实例化
PrismaClient
并获取一个名为prisma
的实例。 - 您调用
express()
来创建您的Express应用程序。 - 您添加
express.json()
中间件以确保Express可以正确处理JSON数据。 - 您在端口3000上启动服务器。
现在您可以实现您的第一个路由了。在app.use
和app.listen
语句之间,添加这些高亮的行来创建一个app.get
调用。
我的博客/源代码/主页.ts
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第8部分(共13部分)。
内容片段:
. . .
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'),
)
添加完成后,保存并退出文件。然后使用如下命令启动本地网页服务器。
- npx ts-node src/index.ts
您将收到以下输出结果:
REST API server ready at: http://localhost:3000
要访问“/users”路径,您可以将浏览器指向http://localhost:3000/users
或使用任何其他HTTP客户端。
在本教程中,您将使用基于终端的HTTP客户端curl
测试所有的REST API路由。
注意:如果您更喜欢使用图形用户界面的HTTP客户端,您可以使用像Hoppscotch或Postman之类的替代工具。为了测试您的路由,打开一个新的终端窗口或标签(以便您的本地Web服务器能继续运行),并执行以下命令。
- curl http://localhost:3000/users
您将收到在上一步中创建的用户数据。
[{“id”:1,”email”:”alice@prisma.io”,”name”:”Alice”}]
这次没有包含帖子数组,因为在实现“/users”路由时没有向findMany
调用传递include
选项。
您已经在/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路由。
您可以通过在键盘上按下CTRL+C
来停止服务器。然后,您可以首先打开index.ts
文件进行编辑以进行更新。
- nano src/index.ts
接下来,在app.get('/users')
路由的实现后面添加高亮的代码行。
. . .
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.findUnique({
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发送的查询会过滤出所有published
列值为true
的Post
记录。此外,Prisma Client查询还使用include
来获取每个返回文章的相关作者信息。在/post/:id
路径的实现中,您将传递从URL路径中检索到的ID,以便从数据库中读取特定的Post
记录。
保存并退出您的文件。然后使用以下命令重新启动服务器:
- npx ts-node src/index.ts
为了测试/feed
路径,您可以在第二个终端会话中使用以下curl
命令:
- curl http://localhost:3000/feed
由于尚未发布任何文章,因此返回的结果为空数组。
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第10部分(共13部分)。
要测试/post/:id
路由,你可以使用以下的curl命令:
- curl http://localhost:3000/post/1
这个命令将返回您最初创建的帖子。
{"id":1,"title":"Hello World","content":null,"published":false,"authorId":1}
接下来,您将实现两个POST路由。在您的原始终端会话中,通过按下CTRL+C停止服务器,然后打开index.ts以进行编辑。
- nano src/index.ts
在index.ts的三个GET路由实现之后,添加上述高亮行代码。
. . .
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的创建查询。
/post
路径比较复杂。您不能直接将HTTP请求的请求体数值传入;相反,您需要手动提取它们以传递给Prisma客户端的查询。因为请求体中的JSON结构与Prisma客户端预期的结构不匹配,所以您需要手动创建预期的结构。
完成后,保存并退出文件。
使用以下命令重新启动服务器:
- 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
这将在数据库中创建一个新用户,并打印以下输出:
{"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
的用户关联起来。它会打印出以下输出:
{"id":2,"title":"I am Bob","content":null,"published":false,"authorId":2}
最后,您将要实现 PUT 和 DELETE 路由。停止开发服务器,然后使用以下命令打开 index.ts
。
- nano src/index.ts
随后,在实现了两个 POST 路由之后,添加上面标记的代码。
. . .
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'),
)
这段代码实现了两个API路由:一个用于PUT请求,另一个用于DELETE请求。
/post/publish/:id
(PUT): 根据ID发布一篇帖子。/post/:id
(DELETE): 根据ID删除一篇帖子。
再次强调,Prisma Client在这两个实现中都被使用。在“/post/publish/:id
”路由的实现中,需要从URL中获取待发布的帖子的ID,并将其传递给Prisma Client的更新查询。而在用于从数据库中删除帖子的“/post/:id
”路由实现中,需要从URL中获取帖子的ID,并将其传递给Prisma Client的删除查询。
保存并退出您的文件。
使用以下方式重新启动服务器:
npx ts-node src/index.ts
您可以使用以下curl命令测试PUT路由:
curl -X PUT http://localhost:3000/post/publish/2
这个命令将帖子的ID值设置为2。如果您重新发送/fed
请求,这个帖子将会在响应中包含。
最后,您可以使用以下curl命令测试删除路由。
curl -X DELETE http://localhost:3000/post/1
这个命令会删除ID为1的帖子。为了验证该ID的帖子已被删除,您可以使用以下curl命令重新发送GET请求到/post/1
路由。
curl http://localhost:3000/post/1
在这一步中,您为博客应用程序实现了剩余的REST API路由。API现在可以响应各种GET、POST、PUT和DELETE请求,并实现了在数据库中读取和写入数据的功能。
结论
这是文章《如何使用Prisma和PostgreSQL构建REST API》的第13部分(共13部分)。
在这篇文章中,您创建了一个REST API服务器,该服务器包含多个不同的路由,用于为示例博客应用程序实现用户的创建、读取、更新和删除,以及发布数据的管理。在API路由内部,您使用Prisma客户端将相应的查询发送到数据库。
作为下一步,您可以利用Prisma Migrate来实施额外的API路由或扩展数据库模式。建议您访问Prisma官方文档,深入了解Prisma的各个方面。此外,您还可以探索prisma-examples存储库,其中包含许多现成的示例项目,例如使用GraphQL或gRPC API的实现。