如何使用 Prisma 和 PostgreSQL 构建 REST API

作者选择了技术多样性基金来接受捐赠,作为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 请求并在数据库中读写数据。

先决条件

本教程假设如下:

基本熟悉 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 进行操作

您将收到与以下类似的输出,并带有默认响应:

Output
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 代码添加到文件中:

我的博客/tsconfig.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

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

我的博客/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

此命令的输出将类似于:

Output
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

这将输出类似于以下内容:

Output
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 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

现在您可以按如下方式设置环境变量:

我的博客/棱镜/.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 语句以创建与您的数据模型对应的表。由于您正在构建一个博客应用程序,应用程序的主要实体将是usersposts

Prisma 使用自己的数据建模语言来定义应用程序数据的形状。

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

  • nano prisma/schema.prisma

现在,向其中添加以下模型定义。您可以将模型放在文件底部,紧跟在generator client块之后:

我的博客/棱镜/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?
}

保存并退出文件。

您正在定义两个模型,称为UserPost其中每一个都有许多代表模型属性的字段模型将映射到数据库表;字段代表各个列。

另外请注意,有一个一对多的关系,两个模型之间,由指定postsauthor相关领域的UserPost这意味着一个用户可以与许多帖子相关联。

有了这些模型,您现在可以使用 Prisma Migrate 在数据库中创建相应的表。在您的终端中运行以下命令:

  • npx prisma migrate dev --name "init" --preview-feature

此命令在您的文件系统上创建一个新的 SQL 迁移并将其发送到数据库。以下是提供给命令的两个选项的快速概览:

  • --name "init":指定迁移的名称(将用于命名在您的文件系统上创建的迁移文件夹)。
  • --preview-feature:必需,因为 Prisma Migrate 当前处于预览状态

此命令的输出将类似于:

Output
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 迁移文件具有以下针对数据库执行的语句:

-- 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在脚本中执行函数添加以下样板

我的博客/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())

这是样板的快速细分:

  1. PrismaClient从以前安装的@prisma/client npm包中导入构造函数
  2. PrismaClient通过调用构造函数进行实例化并获得一个名为 的实例prisma
  3. 您定义了一个async名为函数main,接下来您将在其中添加您的 Prisma 客户端查询。
  4. 您调用该main函数,同时捕获任何潜在的异常并确保 Prisma Client 通过调用prisma.disconnect().

有了该main功能,您就可以开始将 Prisma 客户端查询添加到脚本中。调整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 客户端查询:

  • create: 创建新User记录。请注意,您实际上使用的是嵌套 write,这意味着您在同一个查询中同时创建 aUserPostrecord 。
  • findManyUser从数据库中读取所有现有记录。您提供了include额外加载Post每条User记录的相关记录选项

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

  • npx ts-node src/index.ts

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

Output
Created 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,您可以通过查看UserPost来验证数据是否是创建的或者,您可以通过运行在 Prisma Studio 中探索数据npx prisma studio

您现在已经使用 Prisma Client 在数据库中读取和写入数据。在剩下的步骤中,您将应用这些新知识来实现​​示例 REST API 的路由。

第 5 步 – 实现您的第一个 REST API 路由

在此步骤中,您将在应用程序中安装ExpressExpress 是 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'),
)

这是代码的快速细分:

  1. 从相应的包中导入PrismaClient导入expressnpm
  2. PrismaClient通过调用构造函数进行实例化并获得一个名为 的实例prisma
  3. 您可以通过调用来创建 Express 应用程序express()
  4. 您添加express.json()中间件以确保 Express 可以正确处理 JSON 数据。
  5. 您在端口 上启动服务器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

您将收到以下输出:

Output
REST API server ready at: http://localhost:3000

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

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

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

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

  • curl http://localhost:3000/users

您将收到User您在上一步中创建数据:

Output
[{"id":1,"email":"alice@prisma.io","name":"Alice"}]

请注意,这次posts不包括该数组。这是因为您没有路由的实现中将include选项传递findMany调用/users

您已经在/users. 在下一步中,您将实现剩余的 REST API 路由以向您的 API 添加更多功能。

第 6 步 – 实现剩余的 REST API 路由

在此步骤中,您将为博客应用程序实现其余的 REST API 路由。最后,Web服务器将成为各种GETPOSTPUT,和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 发送的查询会过滤列包含 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路由的实现中

我的博客/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 客户端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":"bob@prisma.io"}' http://localhost:3000/user

这将在数​​据库中创建一个新用户,打印以下输出:

Output
{"id":2,"email":"bob@prisma.io","name":"Bob"}

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

  • 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}

最后,您可以实现PUTDELETE路由。

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路由实现中,从 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使用GraphQLgrPC API等工具探索存储库中的一些准备运行的示例项目

觉得文章有用?

点个广告表达一下你的爱意吧 !😁