作者选择了技术多样性基金来接受捐赠,作为Write for DOnations计划的一部分。
介绍
Prisma是 Node.js 和 TypeScript 的开源 ORM。它由三个主要工具组成:
- Prisma Client:自动生成的类型安全的查询构建器。
- Prisma Migrate:强大的数据建模和迁移系统。
- Prisma Studio:用于查看和编辑数据库中数据的 GUI。
注意: Prisma Migrate 目前处于预览阶段。您可以通过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. 该项目将成为您将在本教程的整个过程中构建的 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 代码添加到文件中:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": ["esnext"],
"esModuleInterop": true
}
}
保存并退出文件。
这是 TypeScript 项目的标准和最小配置。如果您想了解配置文件的各个属性,可以在TypeScript 文档 中查找。
您已经使用npm. 接下来,您将使用 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
现在将以下代码添加到新创建的文件中:
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 文件配置了一个可以通过5432Docker 容器端口访问的 PostgreSQL 数据库。另请注意,数据库凭据当前设置为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 CLIprisma在您的项目中创建了一个名为的新文件夹。它包含以下两个文件:
schema.prisma:您的 Prisma 项目的主要配置文件(将包括您的数据模型)。.env:用于定义数据库连接 URL 的dotenv文件。
要确保 Prisma 知道数据库的位置,请打开.env文件并调整DATABASE_URL环境变量。
首先打开.env文件:
- nano prisma/.env
现在您可以按如下方式设置环境变量:
DATABASE_URL="postgresql://sammy:your_password@localhost:5432/my-blog?schema=public"
确保将数据库凭据更改为您在 Docker Compose 文件中指定的凭据。要了解有关连接 URL 格式的更多信息,请访问Prisma 文档。
完成后,保存并退出文件。
在这一步中,您使用 Docker 设置了 PostgreSQL 数据库,安装了 Prisma CLI,并通过环境变量将 Prisma 连接到数据库。在下一部分中,您将定义数据模型并创建数据库表。
步骤 3 — 定义数据模型并创建数据库表
在此步骤中,您将在 Prisma 模式文件中定义数据模型。然后,这个数据模型将通过Prisma Migrate映射到数据库,它会生成并发送 SQL 语句以创建与您的数据模型对应的表。由于您正在构建一个博客应用程序,应用程序的主要实体将是users和posts。
Prisma 使用自己的数据建模语言来定义应用程序数据的形状。
首先,schema.prisma使用以下命令打开您的文件:
- nano prisma/schema.prisma
现在,向其中添加以下模型定义。您可以将模型放在文件底部,紧跟在generator client块之后:
. . .
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。其中每一个都有许多代表模型属性的字段。模型将映射到数据库表;字段代表各个列。
另外请注意,有一个一对多的关系,两个模型之间,由指定posts及author相关领域的User和Post。这意味着一个用户可以与许多帖子相关联。
有了这些模型,您现在可以使用 Prisma Migrate 在数据库中创建相应的表。在您的终端中运行以下命令:
- npx prisma migrate dev --name "init" --preview-feature
此命令在您的文件系统上创建一个新的 SQL 迁移并将其发送到数据库。以下是提供给命令的两个选项的快速概览:
--name "init":指定迁移的名称(将用于命名在您的文件系统上创建的迁移文件夹)。--preview-feature:必需,因为 Prisma Migrate 当前处于预览状态。
此命令的输出将类似于:
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;
请注意,如果将--create-only选项添加到prisma migrate dev命令中,您还可以自定义生成的 SQL 迁移文件,例如设置触发器或使用底层数据库的其他功能。
在这一步中,您在 Prisma 模式中定义了数据模型,并使用 Prisma Migrate 创建了相应的数据库表。在下一步中,您将在项目中安装 Prisma Client,以便您可以查询数据库。
第 4 步 — 在普通脚本中探索 Prisma 客户端查询
Prisma Client 是一个自动生成且类型安全的查询构建器,您可以使用它以编程方式从 Node.js 或 TypeScript 应用程序读取和写入数据库中的数据。您将使用它在 REST API 路由中访问数据库,取代传统的 ORM、纯 SQL 查询、自定义数据访问层或任何其他与数据库对话的方法。
在这一步中,您将安装 Prisma Client 并熟悉您可以使用它发送的查询。在接下来的步骤中为您的 REST API 实现路由之前,您将首先在一个简单的可执行脚本中探索一些 Prisma 客户端查询。
首先,通过打开终端并安装 Prisma Clientnpm包,继续在您的项目中安装 Prisma Client :
- npm install @prisma/client
接下来,创建一个名为的新目录,该目录src将包含您的源文件:
- mkdir src
现在在新目录中创建一个 TypeScript 文件:
- nano src/index.ts
所有 Prisma 客户端查询都返回您可以在代码中的承诺await。这要求您在async函数内部发送查询。
使用async在脚本中执行的函数添加以下样板:
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())
这是样板的快速细分:
- 您
PrismaClient从以前安装的@prisma/clientnpm包中导入构造函数。 - 您
PrismaClient通过调用构造函数进行实例化并获得一个名为 的实例prisma。 - 您定义了一个
async名为的函数main,接下来您将在其中添加您的 Prisma 客户端查询。 - 您调用该
main函数,同时捕获任何潜在的异常并确保 Prisma Client 通过调用prisma.disconnect().
有了该main功能,您就可以开始将 Prisma 客户端查询添加到脚本中。调整index.ts如下:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const newUser = await prisma.user.create({
data: {
name: 'Alice',
email: '[email protected]',
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 客户端查询:
create: 创建新User记录。请注意,您实际上使用的是嵌套 write,这意味着您在同一个查询中同时创建 aUser和Postrecord 。findMany:User从数据库中读取所有现有记录。您提供了include额外加载Post每条User记录的相关记录的选项。
现在使用以下命令运行脚本:
- npx ts-node src/index.ts
您将在终端中收到以下输出:
OutputCreated new user: { id: 1, email: '[email protected]', name: 'Alice' }
[
{
id: 1,
email: '[email protected]',
name: 'Alice',
posts: [
{
id: 1,
title: 'Hello World',
content: null,
published: false,
authorId: 1
}
]
}
注意:如果您使用的是数据库 GUI,您可以通过查看User和Post表来验证数据是否是创建的。或者,您可以通过运行在 Prisma Studio 中探索数据npx 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:
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'),
)
这是代码的快速细分:
- 您从相应的包中导入
PrismaClient和导入。expressnpm - 您
PrismaClient通过调用构造函数进行实例化并获得一个名为 的实例prisma。 - 您可以通过调用来创建 Express 应用程序
express()。 - 您添加
express.json()中间件以确保 Express 可以正确处理 JSON 数据。 - 您在端口 上启动服务器
3000。
现在您可以实施您的第一条路线。在对app.use和的调用之间app.listen,添加以下代码:
. . .
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 客户端。
在本教程中,您将使用curl基于终端的 HTTP 客户端测试所有 REST API 路由。
注意:如果您更喜欢使用基于 GUI 的 HTTP 客户端,您可以使用Postwoman或Advanced REST Client等替代方案。
要测试您的路由,请打开一个新的终端窗口或选项卡(以便您的本地 Web 服务器可以继续运行)并执行以下命令:
- curl http://localhost:3000/users
您将收到User您在上一步中创建的数据:
Output[{"id":1,"email":"[email protected]","name":"Alice"}]
请注意,这次posts不包括该数组。这是因为您没有在路由的实现中将include选项传递给findMany调用/users。
您已经在/users. 在下一步中,您将实现剩余的 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路由的实现之后添加以下代码:
. . .
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 发送的查询会过滤列包含 value 的所有Post记录。此外,Prisma 客户端查询还用于获取每个返回帖子的相关信息。在路由实现中,您要传递从 URL 路径中检索到的 ID,以便从数据库中读取特定记录。publishedtrueincludeauthor/post/:idPost
您可以停止服务器敲击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路线。将以下代码添加到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 客户端create查询。
的/post路线是一个比较涉及:在这里可以不直接传递在从HTTP请求的主体中的值; 相反,您首先需要手动提取它们以将它们传递给 Prisma 客户端查询。造成这种情况的原因是请求正文中的 JSON 结构与 Prisma Client 预期的结构不匹配,因此您需要手动创建预期结构。
您可以通过使用 停止服务器来测试新路由CTRL+C。然后,使用以下命令重新启动服务器:
- npx ts-node src/index.ts
要通过/user路由创建新用户,您可以发送以下POST请求curl:
- curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob", "email":"[email protected]"}' http://localhost:3000/user
这将在数据库中创建一个新用户,打印以下输出:
Output{"id":2,"email":"[email protected]","name":"Bob"}
要通过/post路由创建新帖子,您可以发送以下POST请求curl:
- curl -X POST -H "Content-Type: application/json" -d '{"title":"I am Bob", "authorEmail":"[email protected]"}' http://localhost:3000/post
这将在数据库中创建一个新帖子并通过电子邮件将其连接到用户[email protected]。它打印以下输出:
Output{"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'),
)
保存并退出您的文件。
此代码为PUT一一DELETE请求实现 API 路由:
/post/publish/:id(PUT):通过其 ID 发布帖子。/post/:id(DELETE): 按 ID 删除帖子。
同样,在这两种实现中都使用了 Prisma Client。在/post/publish/:id路由实现中,从 URL 中获取要发布的帖子的 ID,并传递给updatePrisma Client的查询。/post/:id删除数据库中帖子的路由的实现也会从 URL 中检索帖子 ID,并将其传递给deletePrisma Client的查询。
再次使用CTRL+C键盘上的停止服务器。然后,使用以下命令重新启动服务器:
- npx ts-node src/index.ts
您可以PUT使用以下curl命令测试路由:
- curl -X PUT http://localhost:3000/post/publish/2
这将发布 ID 值为 的帖子2。如果您重新发送/feed请求,此帖子现在将包含在响应中。
最后,您可以DELETE使用以下curl命令测试路由:
- curl -X DELETE http://localhost:3000/post/1
This is going to delete the post with an ID value of 1. To validate that the post with this ID has been deleted, you can resend a GET request to the /post/1 route.
In this step, you implemented the remaining REST API routes for your blogging application. The API now responds to various GET, POST, PUT, and DELETE requests and implements functionality to read and write data in the database.
Conclusion
In this article, you created a REST API server with a number of different routes to create, read, update, and delete user and post data for a sample blogging application. Inside of the API routes, you are using the Prisma Client to send the respective queries to your database.
在接下来的步骤中,您可以使用 Prisma Migrate 实现额外的 API 路由或扩展您的数据库架构。请务必访问Prisma 文档以了解Prisma 的不同方面,并prisma-examples使用GraphQL或grPC API等工具探索存储库中的一些准备运行的示例项目。