如何使用 Prisma 和 PostgreSQL 构建 REST API

作者选择了(https://www.brightfunds.org/funds/diversity-in-tech)和(https://www.brightfunds.org/funds/tech-education)作为 写给捐款计划的一部分的捐款。

介绍

Prisma是Node.js和TypeScript的开源ORM,由三个主要工具组成:

  • Prisma 客户端 :自动生成和安全编写的查询构建程序. * ** Prisma Migrate** :强大的数据建模和迁移系统. * ** Prisma Studio** :用于查看和编辑数据库中的 GUI。

这些工具旨在提高应用程序开发人员在其数据库工作流中的生产力. Prisma 的主要好处之一是其提供的抽象程度:而不是弄清楚复杂的 SQL 查询或方案迁移,应用程序开发人员可以在使用 Prisma 时更直观地推理他们的数据。

在本教程中,您将使用 Prisma 和 PostgreSQL数据库构建一个 REST API 用于在 TypeScript中的小型博客应用程序,您将使用 Docker本地设置您的 PostgreSQL 数据库,并使用 Express实施 REST API 路由。

前提条件

本教程假设如下:

对TypeScript和REST API的基本熟悉是有帮助的,但不需要这个教程。

第1步:创建您的TypeScript项目

在此步骤中,您将使用npm设置一个简单的TypeScript项目,该项目将成为您将在本教程中构建的REST API的基础。

首先,为您的项目创建一个新目录:

1mkdir my-blog

接下来,导航到目录并初始化一个空的npm项目. 请注意,这里的-y选项意味着您正在跳过命令的互动提示。

1cd my-blog
2npm init -y

有关这些提示的详细信息,您可以按照 如何使用 npm 和 package.json 的 Node.js 模块中的步骤 1 进行。

您将收到类似于以下的输出,默认答案在位置:

 1[secondary_label Output]
 2Wrote to /.../my-blog/package.json:
 3
 4{
 5  "name": "my-blog",
 6  "version": "1.0.0",
 7  "description": "",
 8  "main": "index.js",
 9  "scripts": {
10    "test": "echo \"Error: no test specified\" && exit 1"
11  },
12  "keywords": [],
13  "author": "",
14  "license": "ISC"
15}

此命令创建一个最小的 package.json 文件,您将其用作您的 npm 项目的配置文件。

執行下列命令以進行簡單的 TypeScript 設定:

1npm install typescript ts-node @types/node --save-dev

这将安装三个软件包作为项目中的开发依赖:

  • typescript:TypeScript工具链. * ts-node:一个包来运行TypeScript应用程序而无需先编译到JavaScript。

最后要做的是添加一个 tsconfig.json文件,以确保您要构建的应用程序的TypeScript配置正确。

首先,运行以下命令来创建文件:

1nano tsconfig.json

将下列 JSON 代码添加到文件中:

 1[label my-blog/tsconfig.json]
 2{
 3  "compilerOptions": {
 4    "sourceMap": true,
 5    "outDir": "dist",
 6    "strict": true,
 7    "lib": ["esnext"],
 8    "esModuleInterop": true
 9  }
10}

保存和退出文件。

此设置是 TypeScript 项目的标准和最小配置,如果你想了解配置文件的个别属性,可以查看 TypeScript 文档

您已经使用npm设置了您的简单的 TypeScript 项目,接下来您将使用 Docker 设置您的 PostgreSQL 数据库,并将 Prisma 连接到它。

第2步:用PostgreSQL设置 Prisma

在此步骤中,您将安装 Prisma CLI,创建您的初始 Prisma schema文件,并与 Docker 设置 PostgreSQL,并将 Prisma 连接到它。

开始使用以下命令安装 Prisma CLI:

1npm install prisma --save-dev

作为最佳做法,建议您在项目中(而不是作为全球安装)安装 Prisma CLI(本地安装 Prisma CLI)。

接下来,您将使用 Docker 设置您的 PostgreSQL 数据库. 使用以下命令创建一个新的 Docker Compose 文件:

1nano docker-compose.yml

现在将以下代码添加到新创建的文件中:

 1[label my-blog/docker-compose.yml]
 2version: '3.8'
 3services:
 4  postgres:
 5    image: postgres:10.3
 6    restart: always
 7    environment:
 8      - POSTGRES_USER=sammy
 9      - POSTGRES_PASSWORD=your_password
10    volumes:
11      - postgres:/var/lib/postgresql/data
12    ports:
13      - '5432:5432'
14volumes:
15  postgres:

此 Docker Compose 文件配置了一个 PostgreSQL 数据库,可以通过 Docker 容器的端口 5432 访问。 数据库凭据目前设置为 sammy' (用户) 和 your_password' (密码)。 请自由将这些凭据调整为您喜爱的用户和密码。 保存和退出文件。

有了此设置,请使用以下命令启动 PostgreSQL 数据库服务器:

1docker-compose up -d

这个命令的输出将类似于此:

1[secondary_label Output]
2Pulling postgres (postgres:10.3)...
310.3: Pulling from library/postgres
4f2aa67a397c4: Pull complete
56de83ca23e55: Pull complete
6. . .
7Status: Downloaded newer image for postgres:10.3
8Creating my-blog_postgres_1 ... done

您可以通过以下命令验证数据库服务器是否正在运行:

1docker ps

这个命令将输出类似于此的东西:

1[secondary_label Output]
2CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
38547f8e007ba 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 运行以下命令:

1npx prisma init

此命令将打印以下输出:

1[secondary_label Output]
2✔ Your Prisma schema was created at prisma/schema.prisma.
3  You can now open it in your favorite editor.

作为最佳做法,您应该将Prisma CLI的所有召唤前缀为npx,以确保您的本地安装正在使用。

在运行命令后,Prisma CLI在您的项目中创建了一个名为prisma的新文件夹. 您将在其内部找到一个schema.prisma文件,这是您的Prisma项目的主要配置文件(包括您的数据模型)。

要确保 Prisma 知道您的数据库的位置,请打开.env 文件并调整环境变量。

首先打开.env 文件:

1nano .env

现在,您可以更新环境变量如下:

1[label my-blog/.env]
2DATABASE_URL="postgresql://sammy:your_password@localhost:5432/my-blog?schema=public"

请确保将数据库凭据更改为您在 Docker Compose 文件中指定的凭据。 有关连接 URL 格式的更多信息,请访问 Prisma docs

一旦完成,保存并退出文件。

在此步骤中,您将使用 Docker 设置您的 PostgreSQL 数据库,安装 Prisma CLI,并通过环境变量连接 Prisma 到数据库。

第3步:定义您的数据模型和创建数据库表

在此步骤中,您将在 Prisma 方案文件中定义您的 数据模型 然后将该数据模型与 Prisma Migrate 对照到数据库中,该数据模型将生成并发送 SQL 陈述来创建与您的数据模型相符的表格。

Prisma 使用自己的数据建模语言(https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema# syntax)来定义应用数据的形状。

首先,用以下命令打开你的schema.prisma文件:

1nano prisma/schema.prisma

现在,添加以下模型定义,您可以将模型放置在文件的底部,就在发电机客户端块后:

 1[label my-blog/prisma/schema.prisma]
 2. . .
 3model User {
 4  id Int     @default(autoincrement()) @id
 5  email String  @unique
 6  name String?
 7  posts Post[]
 8}
 9
10model Post {
11  id Int     @default(autoincrement()) @id
12  title String
13  content String?
14  published Boolean @default(false)
15  author User?   @relation(fields: [authorId], references: [id])
16  authorId Int?
17}

您正在定义两个模型(https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/models):`用户邮件`。 每个模型都有代表模型的属性的一些 字段

两个模型之间存在一个对许多的关系(https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations),由用户帖子上的帖子作者的关系字段指定。

保存和退出文件。

有了这些模型,您现在可以使用 Prisma Migrate 在数据库中创建相应的表。

1npx prisma migrate dev --name init

此命令会在您的文件系统上创建一个新的 SQL 迁移,并将其发送到数据库. 指令中提供的 --name init 选项将指定迁移的名称,并将用于在您的文件系统上创建的迁移文件夹。

这个命令的输出将类似于此:

 1[secondary_label Output]
 2Environment variables loaded from .env
 3Prisma schema loaded from prisma/schema.prisma
 4Datasource "db": PostgreSQL database "my-blog", schema "public" at "localhost:5432"
 5
 6PostgreSQL database my-blog created at localhost:5432
 7
 8The following migration(s) have been created and applied from new schema changes:
 9
10migrations/
11  └─ 20201209084626_init/
12    └─ migration.sql
13
14Running generate... (Use --skip-generate to skip the generators)
15
16 Generated Prisma Client (2.13.0) to ./node_modules/@prisma/client in 75ms

prisma/migrations/20201209084626_init/migration.sql目录中的 SQL 迁移文件中,对数据库进行了执行的下列陈述(文件名中的突出部分在您的设置中可能有所不同):

 1[label prisma/migrations/20201209084626_init/migration.sql]
 2-- CreateTable
 3CREATE TABLE "User" (
 4"id" SERIAL,
 5    "email" TEXT NOT NULL,
 6    "name" TEXT,
 7
 8    PRIMARY KEY ("id")
 9);
10
11-- CreateTable
12CREATE TABLE "Post" (
13"id" SERIAL,
14    "title" TEXT NOT NULL,
15    "content" TEXT,
16    "published" BOOLEAN NOT NULL DEFAULT false,
17    "authorId" INTEGER,
18
19    PRIMARY KEY ("id")
20);
21
22-- CreateIndex
23CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
24
25-- AddForeignKey
26ALTER TABLE "Post" ADD FOREIGN KEY("authorId")REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

您还可以自定义生成的 SQL 迁移文件,如果您将 --create-only 选项添加到 prisma migrate dev 命令中;例如,您可以设置触发器或使用潜在数据库的其他功能。

在此步骤中,您在 Prisma 方案中定义了数据模型,并使用 Prisma Migrate 创建了相应的数据库表。

步骤4 — 在简单的脚本中探索 Prisma 客户端查询

Prisma 客户端是一个自动生成和类型安全的查询构建程序,您可以使用它来从 Node.js 或 TypeScript 应用程序中编程读取和写入数据库中的数据,您将使用它用于 REST API 路径中的数据库访问,取代传统的 ORM,简单的 SQL 查询,自定义数据访问层或任何其他方法来与数据库交谈。

在此步骤中,您将安装 Prisma 客户端并熟悉您可以发送的查询,然后在下一个步骤中实现 REST API 的路径之前,您将首先在简单的可执行脚本中探索一些 Prisma 客户端查询。

首先,在您的项目文件夹中安装 Prisma 客户端,使用 Prisma 客户端 npm 包:

1npm install @prisma/client

接下来,创建一个名为src的新目录,该目录将包含您的源文件:

1mkdir src

现在,在新目录中创建一个 TypeScript 文件:

1nano src/index.ts

所有 Prisma 客户端查询都返回 承诺,您可以在代码中等待

在「src/index.ts」文件中,添加下列 boilerplate 函数「async」函数,该函数在你的脚本中执行:

 1[label my-blog/src/index.ts]
 2import { PrismaClient } from '@prisma/client'
 3
 4const prisma = new PrismaClient()
 5
 6async function main() {
 7  // ... your Prisma Client queries will go here
 8}
 9
10main()
11  .catch((e) => console.error(e))
12  .finally(async () => await prisma.$disconnect())

以下是锅炉板的快速分解:

您从先前安装的@prisma/client``npm包中导入了PrismaClient构建器,2.您通过呼叫构建器并获得一个名为prisma的实例来实时化PrismaClient。3您定义了一个名为mainasync函数,在那里您将添加您的Prisma Client查询。4您呼叫main函数,捕捉任何潜在的例外,并确保Prisma Client通过prisma.$disconnect()关闭任何开放的数据库连接。

有了主要函数,您可以开始将 Prisma 客户端查询添加到脚本中。

 1[label my-blog/src/index.ts]
 2import { PrismaClient } from '@prisma/client'
 3
 4const prisma = new PrismaClient()
 5
 6async function main() {
 7  const newUser = await prisma.user.create({
 8    data: {
 9      name: 'Alice',
10      email: '[email protected]',
11      posts: {
12        create: {
13          title: 'Hello World',
14        },
15      },
16    },
17  })
18  console.log('Created new user: ', newUser)
19
20  const allUsers = await prisma.user.findMany({
21    include: { posts: true },
22  })
23  console.log('All users: ')
24  console.dir(allUsers, { depth: null })
25}
26
27main()
28  .catch((e) => console.error(e))
29  .finally(async () => await prisma.$disconnect())

在此代码中,您正在使用两个 Prisma 客户端查询:

  • create: 创建一个新的 User 记录. 您使用一个 nested write 查询在同一个查询中创建一个 User 和一个 Post 记录。 * findMany: 从数据库中读取所有现有的 User 记录。

保存并关闭文件。

现在用以下命令运行脚本:

1npx ts-node src/index.ts

您将在您的终端中收到以下输出:

 1[secondary_label Output]
 2Created new user:  { id: 1, email: '[email protected]', name: 'Alice' }
 3[
 4  {
 5    id: 1,
 6    email: '[email protected]',
 7    name: 'Alice',
 8    posts: [
 9      {
10        id: 1,
11        title: 'Hello World',
12        content: null,
13        published: false,
14        authorId: 1
15      }
16    ]
17  }

注意: 如果您正在使用数据库 GUI,您可以通过查看用户邮件表来验证数据的创建,也可以通过运行npx prisma studio在Prisma Studio中探索数据。

您现在已经使用 Prisma Client 读取和写入数据库中的数据,在剩余的步骤中,您将实现样本 REST API 的路径。

步骤5 - 实施您的第一个 REST API 路径

在此步骤中,您将安装 Express在您的应用程序中。Express 是 Node.js 的流行的 Web 框架,您将在该项目中使用它来实现 REST API 路由。

使用以下命令安装 Express:

1npm install express

由于您正在使用 TypeScript,您还需要将相应类型安装为开发依赖。

1npm install @types/express --save-dev

有了依赖性,您可以设置您的Express应用程序。

重新打开主源文件:

1nano src/index.ts

现在删除index.ts中的所有代码,并用以下代码来启动 REST API:

 1[label my-blog/src/index.ts]
 2import { PrismaClient } from '@prisma/client'
 3import express from 'express'
 4
 5const prisma = new PrismaClient()
 6const app = express()
 7
 8app.use(express.json())
 9
10// ... your REST API routes will go here
11
12app.listen(3000, () =>
13  console.log('REST API server ready at: http://localhost:3000'),
14)

以下是代码的快速分解:

你从各自的npm包中导入PrismaClientexpress。2你通过调用构建器来实时化PrismaClient,并获得一个名为prisma的实例。3你通过调用express()来创建你的Express应用。4你添加了express.json()中间软件,以确保Express能正确处理JSON数据。5你在端口3000上启动服务器。

app.useapp.listen的呼叫之间,添加突出的行以创建app.get呼叫:

 1[label my-blog/src/index.ts]
 2. . .
 3app.use(express.json())
 4
 5app.get('/users', async (req, res) => {
 6  const users = await prisma.user.findMany()
 7  res.json(users)
 8})
 9
10app.listen(3000, () =>
11console.log('REST API server ready at: http://localhost:3000'),
12)

添加后,保存并退出文件,然后使用以下命令启动本地 Web 服务器:

1npx ts-node src/index.ts

您将获得以下输出:

1[secondary_label Output]
2REST API server ready at: http://localhost:3000

要访问用户路线,您可以将浏览器指向 http://localhost:3000/users或任何其他HTTP客户端。

在本教程中,您将使用基于终端的 HTTP 客户端 curl测试所有 REST API 路径。

注意: 如果您更喜欢使用基于 GUI 的 HTTP 客户端,您可以使用 HoppscotchPostman等替代方案。

要测试路线,请打开一个新的终端窗口或选项卡(以便您的本地 Web 服务器可以继续运行),并执行以下命令:

1[environment second]
2curl http://localhost:3000/users

您将收到您在上一步创建的用户数据:

1[environment second]
2[secondary_label Output]
3[{"id":1,"email":"[email protected]","name":"Alice"}]

这次不包含帖子数组,因为在实施/用户路径时,您没有将包括选项转移到findMany呼叫中。

您已在 /users 上实现了第一个 REST API 路线,下一步您将部署剩余的 REST API 路线,以便为您的 API 添加更多功能。

步骤6 – 实施剩余的 REST API 路径

在此步骤中,您将为您的博客应用程序实施剩余的 REST API 路径. 最终,您的 Web 服务器将服务于各种GET,POST,PUTDELETE请求。

您将实施的路线包括以下选项:

HTTP MethodRouteDescription
GET/feedFetches all published posts.
GET/post/:idFetches a specific post by its ID.
POST/userCreates a new user.
POST/postCreates a new post (as a draft).
PUT/post/publish/:idSets the published field of a post to true.
DELETEpost/:idDeletes a post by its ID.

您将首先执行剩余的两个GET路径。

您可以通过在键盘上按CTRL+C来阻止服务器,然后,您可以通过先打开文件进行编辑来更新您的index.ts文件:

1nano src/index.ts

接下来,添加/app.get用户路线的实施后所突出的行:

 1[label my-blog/src/index.ts]
 2. . .
 3
 4app.get('/feed', async (req, res) => {
 5  const posts = await prisma.post.findMany({
 6    where: { published: true },
 7    include: { author: true }
 8  })
 9  res.json(posts)
10})
11
12app.get(`/post/:id`, async (req, res) => {
13  const { id } = req.params
14  const post = await prisma.post.findUnique({
15    where: { id: Number(id) },
16  })
17  res.json(post)
18})
19
20app.listen(3000, () =>
21  console.log('REST API server ready at: http://localhost:3000'),
22)

此代码实现了两个GET请求的API路径:

  • /feed: 返回发布帖子的列表. * /post/:id: 返回特定帖子以其ID。

Prisma 客户端在两种实现中都使用。在/feed路径实现中,您通过 Prisma 客户端过滤器发送的查询用于所有发布列包含真实值的邮件记录。此外,Prisma 客户端查询还使用包括来获取每个返回的邮件的相关作者信息。

保存并退出您的文件,然后使用以下方式重新启动服务器:

1npx ts-node src/index.ts

要测试/feed路径,您可以在第二个终端会话中使用以下curl命令:

1[environment second]
2curl http://localhost:3000/feed

由于尚未发布任何帖子,答案是一个空数组:

1[environment second]
2[secondary_label Output]
3[]

要测试 /post/:id 路径,您可以使用以下 curl 命令:

1[environment second]
2curl http://localhost:3000/post/1

此命令将返回您最初创建的帖子:

1[environment second]
2[secondary_label Output]
3{"id":1,"title":"Hello World","content":null,"published":false,"authorId":1}

在原始终端会话中,用CTRL+C停止服务器,然后打开index.ts进行编辑:

1nano src/index.ts

将突出的行列添加到index.ts中,然后执行三个GET路径:

 1[label my-blog/src/index.ts]
 2. . .
 3
 4app.post(`/user`, async (req, res) => {
 5  const result = await prisma.user.create({
 6    data: { ...req.body },
 7  })
 8  res.json(result)
 9})
10
11app.post(`/post`, async (req, res) => {
12  const { title, content, authorEmail } = req.body
13  const result = await prisma.post.create({
14    data: {
15      title,
16      content,
17      published: false,
18      author: { connect: { email: authorEmail } },
19    },
20  })
21  res.json(result)
22})
23
24app.listen(3000, () =>
25  console.log('REST API server ready at: http://localhost:3000'),
26)

此代码实现了两个POST请求的 API 路径:

  • /user:在数据库中创建一个新用户。

/user路径实现中,您将从 HTTP 请求的身体传输到 Prisma 客户端创建查询的值。

你不能直接从 HTTP 请求的体内传输值;相反,你需要先将它们手动提取,以便将它们传输到 Prisma 客户端查询中。

一旦完成,保存并退出您的文件。

重启服务器使用:

1npx ts-node src/index.ts

要通过/user路径创建新用户,您可以通过curl发送以下POST请求:

1[environment second]
2curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob", "email":"[email protected]"}' http://localhost:3000/user

这将创建数据库中的新用户,打印以下输出:

1[environment second]
2[secondary_label Output]
3{"id":2,"email":"[email protected]","name":"Bob"}

要通过/post路径创建新帖子,您可以通过curl发送以下POST请求:

1[environment second]
2curl -X POST -H "Content-Type: application/json" -d '{"title":"I am Bob", "authorEmail":"[email protected]"}' http://localhost:3000/post

这将在数据库中创建一个新的帖子,并通过电子邮件[email protected]将其连接到用户。

1[environment second]
2[secondary_label Output]
3{"id":2,"title":"I am Bob","content":null,"published":false,"authorId":2}

最后,您将执行PUTDELETE路径. 停止开发服务器,然后使用以下命令打开index.ts:

1nano src/index.ts

接下来,在实现两个POST路径后,添加突出的代码:

 1[label my-blog/src/index.ts]
 2. . .
 3
 4app.put('/post/publish/:id', async (req, res) => {
 5  const { id } = req.params
 6  const post = await prisma.post.update({
 7    where: { id: Number(id) },
 8    data: { published: true },
 9  })
10  res.json(post)
11})
12
13app.delete(`/post/:id`, async (req, res) => {
14  const { id } = req.params
15  const post = await prisma.post.delete({
16    where: { id: Number(id) },
17  })
18  res.json(post)
19})
20
21app.listen(3000, () =>
22  console.log('REST API server ready at: http://localhost:3000'),
23)

此代码实现了一个PUT和一个DELETE请求的API路径:

  • /post/publish/:id(PUT):以其ID发布帖子. * /post/:id(DELETE):以其ID删除帖子。

/post/publish/:id路径实现中,要发布的帖子的ID从URL中获取并传递到Prisma客户端的更新查询中。

保存和退出您的文件。

重启服务器使用:

1npx ts-node src/index.ts

您可以使用以下curl命令测试PUT路线:

1[environment second]
2curl -X PUT http://localhost:3000/post/publish/2

此命令将以2的ID值发布帖子. 如果您重新发送/feed请求,此帖子将被包含在回复中。

最后,您可以使用以下curl命令测试DELETE路径:

1[environment second]
2curl -X DELETE http://localhost:3000/post/1

此命令会删除具有1 ID 值的邮件. 若要验证具有此 ID 的邮件已被删除,您可以使用以下curl命令将GET请求重新发送到/post/1路线:

1[environment second]
2curl http://localhost:3000/post/1

在此步骤中,您已经为您的博客应用程序实现了剩余的 REST API 路径,该 API 现在响应各种GET,POST,PUTDELETE请求,并实现可读和写入数据库的功能。

结论

在本文中,您创建了一台 REST API 服务器,使用多种不同的路径来创建、阅读、更新和删除用户和发布样本博客应用程序的数据。

作为下一个步骤,您可以使用 Prisma Migrate 实现额外的 API 路径或扩展您的数据库架构。 访问 Prism 文档(https://www.prisma.io/docs)以了解 Prisma 的不同方面,并使用 GraphQLgrPC API 等工具探索一些准备运行的示例项目。

Published At
Categories with 技术
comments powered by Disqus