如何使用 Prisma 构建 GraphQL API 并部署到 DigitalOcean 的应用程序平台

作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。

介绍

GraphQL是一种用于 API 的查询语言,它由架构定义语言和查询语言组成,它允许 API 使用者仅获取他们需要的数据以支持灵活查询。GraphQL 使开发人员能够在满足多个客户端(例如 iOS、Android 和应用程序的 Web 变体)的不同需求的同时改进 API。此外,GraphQL 模式为 API 增加了一定程度的类型安全性,同时也作为 API 的一种文档形式。

Prisma是一个开源数据库工具包。它由三个主要工具组成:

  • Prisma Client:用于 Node.js 和 TypeScript 的自动生成和类型安全的查询构建器。
  • Prisma Migrate:声明式数据建模和迁移系统。
  • Prisma Studio:用于查看和编辑数据库中数据的 GUI。

Prisma 为希望专注于实现增值功能而不是将时间花在复杂的数据库工作流(例如模式迁移或编写复杂的 SQL 查询)上的应用程序开发人员使用数据库提供便利。

在本教程中,您将结合使用 GraphQL 和 Prisma,因为它们的职责相辅相成。GraphQL 为您的数据提供了一个灵活的接口,以便在客户端(例如前端和移动应用程序)中使用——GraphQL 不依赖于任何特定的数据库。这就是 Prisma 处理与存储数据的数据库交互的地方。

DigitalOcean 的应用程序平台提供了一种在云中部署应用程序和配置数据库的无缝方式,而无需担心基础设施。这减少了在云中运行应用程序的运营开销;尤其是能够创建具有每日备份和自动故障转移的托管 PostgreSQL 数据库。App Platform 具有原生 Node.js 支持简化部署。

您将使用 Node.js 在 JavaScript 中为博客应用程序构建 GraphQL API。您将首先使用 Apollo Server 构建由内存数据结构支持的 GraphQL API。然后,您将把 API 部署到 DigitalOcean 应用平台。最后,您将使用 Prisma 替换内存中的存储并将数据保存在 PostgreSQL 数据库中并再次部署应用程序。

在本教程结束时,您将有一个 Node.js GraphQL API 部署到 DigitalOcean,它处理通过 HTTP 发送的 GraphQL 请求并对 PostgreSQL 数据库执行 CRUD 操作。

您可以在DigitalOcean 社区资源库中找到该项目的代码

先决条件

在开始本指南之前,您需要具备以下条件:

基本熟悉JavaScriptNode.js、 GraphQL 和 PostgreSQL 很有帮助,但本教程并不严格要求。

第 1 步 – 创建 Node.js 项目

在这一步中,您将使用 npm 设置一个 Node.js 项目并安装依赖项apollo-servergraphql.

该项目将是您将在本教程中构建和部署的 GraphQL API 的基础。

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

  • mkdir prisma-graphql

接下来,导航到该目录并初始化一个空的 npm 项目:

  • cd prisma-graphql
  • npm init --yes

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

您将收到以下输出:

Output
Wrote to /Users/yourusaername/workspace/prisma-graphql/package.json: { "name": "prisma-graphql", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

您现在已准备好在您的项目中配置 TypeScript。

执行以下命令安装必要的依赖项:

  • npm install apollo-server graphql --save

这会在您的项目中安装两个包作为依赖项:

  • apollo-server:用于定义如何解析 GraphQL 请求以及如何获取数据的 HTTP 库。
  • graphql: 是您将用于构建 GraphQL 架构的库。

您已经创建了项目并安装了依赖项。在下一步中,您将定义 GraphQL 架构。

第 2 步 – 定义 GraphQL 模式和解析器

在这一步中,您将定义 GraphQL架构和相应的解析器模式将定义 API 可以处理的操作。解析器将使用内存数据结构定义处理这些请求的逻辑,您将在下一步中将其替换为数据库查询。

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

  • mkdir src

然后运行以下命令为架构创建文件:

  • nano src/schema.js

现在将以下代码添加到文件中:

棱镜graphql/src/schema.js
const { gql } = require('apollo-server')

const typeDefs = gql`
  type Post {
    content: String
    id: ID!
    published: Boolean!
    title: String!
  }

  type Query {
    feed: [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createDraft(content: String, title: String!): Post!
    publish(id: ID!): Post
  }
`

在这里,您使用gql 标记模板定义 GraphQL 模式模式是一组类型定义(因此typeDefs),它们共同定义了可以针对您的 API 执行的查询的形状。这会将 GraphQL 模式字符串转换为 Apollo 期望的格式。

该模式引入了三种类型:

  • Post:定义博客应用程序中帖子的类型并包含四个字段,其中每个字段后跟其类型,例如,String
  • Query: 定义feed返回由方括号表示的多个帖子的post查询和接受单个参数并返回单个Post.
  • Mutation: 定义createDraft用于创建草稿Postpublish突变和接受 anid并返回 a突变Post

请注意,每个 GraphQL API 都有一个查询类型,并且可能有也可能没有突变类型。这些类型与常规对象类型相同,但它们很特殊,因为它们定义了每个 GraphQL 查询的入口点。

接下来,将posts数组添加src/schema.js文件中,在typeDefs变量下方

棱镜graphql/src/schema.js
...
const posts = [
  {
    id: 1,
    title: 'Subscribe to GraphQL Weekly for community news ',
    content: 'https://graphqlweekly.com/',
    published: true,
  },
  {
    id: 2,
    title: 'Follow DigitalOcean on Twitter',
    content: 'https://twitter.com/digitalocean',
    published: true,
  },
  {
    id: 3,
    title: 'What is GraphQL?',
    content: 'GraphQL is a query language for APIs',
    published: false,
  },
]

posts使用三个预定义的帖子定义数组。请注意,每个post对象的结构都Post您在架构中定义类型相匹配该数组包含将由 API 提供的帖子。在后续步骤中,您将在引入数据库和 Prisma 客户端后替换阵列。

接下来,在刚刚定义resolversposts数组下面定义对象

棱镜graphql/src/schema.js
...
const resolvers = {
  Query: {
    feed: (parent, args) => {
      return posts.filter((post) => post.published)
    },
    post: (parent, args) => {
      return posts.find((post) => post.id === Number(args.id))
    },
  },
  Mutation: {
    createDraft: (parent, args) => {
      posts.push({
        id: posts.length + 1,
        title: args.title,
        content: args.content,
        published: false,
      })
      return posts[posts.length - 1]
    },
    publish: (parent, args) => {
      const postToPublish = posts.find((post) => post.id === Number(args.id))
      postToPublish.published = true
      return postToPublish
    },
  },
  Post: {
    content: (parent) => parent.content,
    id: (parent) => parent.id,
    published: (parent) => parent.published,
    title: (parent) => parent.title,
  },
}


module.exports = {
  resolvers,
  typeDefs,
}

您可以按照与 GraphQL 架构相同的结构来定义解析器。模式类型中的每个字段都有一个相应的解析器函数,其职责是返回模式中该字段的数据。例如,Query.feed()解析器将通过过滤posts数组来返回已发布的帖子

解析器函数接收四个参数:

  • parent: parent 是解析器链中前一个解析器的返回值。对于顶级解析器,父解析器是undefined,因为没有调用先前的解析器。例如,在进行feed查询时,query.feed()将使用parent的值调用undefined解析器,然后在解析器返回的对象Post所在的位置调用parentfeed解析器。
  • args: 该参数携带查询的参数,例如post查询,将接收id要获取的帖子的 。
  • context:通过解析器链传递的对象,每个解析器都可以写入和读取,这允许解析器共享信息。
  • info:查询或突变的 AST 表示。您可以在本系列的第三部分阅读更多关于细节的信息揭开 GraphQL 解析器中的信息参数的神秘面纱

由于contextinfo这些解析器是没有必要的,只有parentargs定义。

完成后保存并退出文件。

注意:当解析器返回与解析器名称相同的字段时,例如 的四个解析器Post,Apollo Server 将自动解析它们。这意味着您不必明确定义这些解析器。

-  Post: {
-    content: (parent) => parent.content,
-    id: (parent) => parent.id,
-    published: (parent) => parent.published,
-    title: (parent) => parent.title,
-  },

最后,您导出模式和解析器,以便您可以在下一步中使用它们通过 Apollo Server 实例化服务器。

第 3 步 – 创建 GraphQL 服务器

在这一步中,您将使用 Apollo Server 创建 GraphQL 服务器并将其绑定到一个端口,以便服务器可以接受连接。

首先,运行以下命令为服务器创建文件:

  • nano src/server.js

现在将以下代码添加到文件中:

棱镜graphql/src/server.js
const { ApolloServer } = require('apollo-server')
const { resolvers, typeDefs } = require('./schema')

const port = process.env.PORT || 8080

new ApolloServer({ resolvers, typeDefs }).listen({ port }, () =>
  console.log(`Server ready at: http://localhost:${port}`),
)

在这里,您实例化服务器并传递上一步中的架构和解析器。

服务器将绑定到的端口是从PORT环境变量设置的,如果未设置,它将默认为8080. PORT环境变量将被应用平台自动设置,并确保您的服务器可以接受连接一旦部署。

保存并退出文件。

您的 GraphQL API 已准备好运行。使用以下命令启动服务器:

  • node src/server.js

您将收到以下输出:

Output
Server ready at: http://localhost:8080

将启动脚本添加package.json到您的服务器被认为是一种很好的做法,以便您的服务器的入口点清晰。此外,这将允许 App Platform 在部署后启动服务器。

为此,"scripts"将以下行添加到 中的对象package.json

包.json
{
  "name": "prisma-graphql",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node ./src/server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server": "^2.18.2",
    "graphql": "^15.3.0"
  }
}

完成后保存并退出文件。

现在您可以使用以下命令启动服务器:

  • npm start

要测试 GraphQL API,请打开输出中的 URL,这将引导您进入 GraphQL Playground。

GraphQL 游乐场

GraphQL Playground 是一个 IDE,您可以在其中通过发送查询和更改来测试 API。

例如,要测试feed仅返回已发布帖子的查询,请在 IDE 左侧输入以下查询,然后按播放按钮发送查询:

query {
  feed {
    id
    title
    content
    published
  }
}

响应将显示Subscribe to GraphQL Weekly带有其 URL 和 URL的标题Follow DigitalOcean on Twitter

GraphQL 查询

要测试createDraft突变,请输入以下突变:

mutation {
  createDraft(title: "Deploying a GraphQL API to DigitalOcean") {
    id
    title
    content
    published
  }
}

在您提交变更后,使用播放按钮,您将Deploying a GraphQL API to DigitalOceantitle字段收到作为响应的一部分。

GraphQL 突变

注意:您可以通过添加或删除后面的大括号内的字段来选择从突变中返回哪些字段createDraft例如,如果您只想返回id并且title您可以发送以下更改

mutation {
  createDraft(title: "Deploying a GraphQL API to DigitalOcean") {
    id
    title
  }
}

您已成功创建并测试了 GraphQL 服务器。在下一步中,您将为项目创建一个 GitHub 存储库。

第 4 步 – 创建 GitHub 存储库

在这一步中,您将为您的项目创建一个 GitHub 存储库并推送您的更改,以便 GraphQL API 可以从 GitHub 自动部署到应用程序平台。

首先从prisma-graphql文件夹初始化存储库

  • git init

接下来,使用以下两个命令将代码提交到存储库:

  • git add src package-lock.json package.json
  • git commit -m 'Initial commit'

现在更改已提交到您的本地存储库,您将在 GitHub 中创建一个存储库并推送您的更改。

转到GitHub以创建新存储库。为保持一致性,将存储库命名为prisma-graphql,然后单击Create repository

创建存储库后,使用以下命令推送更改,其中包括将默认本地分支重命名为main

  • git remote add origin [email protected]:your_github_username/prisma-graphql.git
  • git branch -M main
  • git push --set-upstream origin main

您已成功提交更改并将更改推送到 GitHub。接下来,您将存储库连接到 App Platform 并部署 GraphQL API。

第 5 步 – 部署到应用程序平台

在这一步中,您将在上一步中创建的 GitHub 存储库连接到 DigitalOcean 并配置 App Platform,以便在您将更改推送到 GitHub 时可以自动部署 GraphQL API。

首先,转到应用程序平台并单击启动您的应用程序按钮。

您将看到一个链接您的 GitHub 帐户的按钮。

链接您的 GitHub 帐户

单击它,您将被重定向到 GitHub。

单击安装和授权,您将被重定向回 DigitalOcean。

选择存储库your_github_username/prisma-graphql并单击Next

命名您的应用程序并选择一个分支进行部署

选择要将应用程序部署到的区域,然后单击下一步

配置您的应用

您可以在此处自定义应用程序的配置。确保运行命令npm start. 默认情况下,App Platform 会将 HTTP 端口设置为8080,这与您将 GraphQL 服务器配置为绑定到的端口相同。

单击下一步,系统将提示您选择计划。

选择Basic,然后单击Launch Basic App您将被重定向到应用程序页面,您将在其中看到初始部署的进度。

构建完成后,您将收到一条通知,指示您的应用程序已部署。

应用页面

您现在可以通过应用名称下方的 URL 访问已部署的 GraphQL API。它将在ondigitalocean.app子域下。如果您打开 URL,GraphQL Playground 将以与教程第 3 步中相同的方式打开。

您已成功将您的存储库连接到 App Platform 并部署了您的 GraphQL API。接下来,您将改进您的应用程序并用数据库替换 GraphQL API 的内存数据。

第 6 步 — 使用 PostgreSQL 设置 Prisma

到目前为止,您构建的 GraphQL API 使用内存posts数组来存储数据。这意味着如果您的服务器重新启动,对数据的所有更改都将丢失。为确保您的数据安全持久化,您将posts使用 PostgreSQL 数据库替换数组并使用 Prisma 访问数据。

在这一步中,您将安装 Prisma CLI,创建您的初始 Prisma 模式,使用 Docker 在本地设置 PostgreSQL,并将 Prisma 连接到它。

Prisma 架构是 Prisma 设置的主要配置文件,包含您的数据库架构。

首先使用以下命令安装 Prisma CLI:

  • npm install --save-dev @prisma/cli

Prisma CLI 将帮助处理数据库工作流,例如运行数据库迁移和生成 Prisma 客户端。

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

  • nano docker-compose.yml

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

棱镜graphql/docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:10.3
    restart: always
    environment:
      - POSTGRES_USER=test-user
      - POSTGRES_PASSWORD=test-password
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'
volumes:
  postgres:

这个 Docker Compose 配置文件负责在你的机器上启动官方的 PostgreSQL Docker 镜像。POSTGRES_USERPOSTGRES_PASSWORD环境变量设置的凭据超级用户(具有管理员权限的用户)。您还将使用这些凭据将 Prisma 连接到数据库。最后,您定义一个 PostgreSQL 将在其中存储其数据的卷,并将5432您机器上的端口绑定到 Docker 容器中的同一端口。

保存并退出文件。

完成此设置后,继续使用以下命令启动 PostgreSQL 数据库服务器:

  • docker-compose up -d

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

  • docker ps

这将输出类似于:

Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 198f9431bf73 postgres:10.3 "docker-entrypoint.s…" 45 seconds ago Up 11 seconds 0.0.0.0:5432->5432/tcp prisma-graphql_postgres_1

随着 PostgreSQL 容器的运行,您现在可以创建您的 Prisma 设置。从 Prisma CLI 运行以下命令:

  • npx prisma init

请注意,作为最佳实践,Prisma CLI 的所有调用都应以npx. 这确保它使用您的本地安装。

运行该命令后,Prisma CLIprisma在您的项目中创建了一个名为的新文件夹它包含以下两个文件:

  • schema.prisma:您的 Prisma 项目的主要配置文件(您将在其中包含您的数据模型)。
  • .env用于定义数据库连接 URL 的dotenv文件。

要确保 Prisma 知道您的数据库的位置,请打开.env文件:

  • nano prisma/.env

调整DATABASE_URL环境变量如下:

棱镜graphql/棱镜/.env
DATABASE_URL="postgresql://test-user:test-password@localhost:5432/my-blog?schema=public"

请注意,您正在使用Docker Compose 文件中指定的数据库凭据test-usertest-password要了解有关连接 URL 格式的更多信息,请访问Prisma 文档

您已经成功启动了 PostgreSQL 并使用 Prisma 模式配置了 Prisma。在下一步中,您将为博客定义数据模型并使用 Prisma Migrate 创建数据库模式。

步骤 7 — 使用 Prisma Migrate 定义数据模型

现在,您将在刚刚创建的 Prisma 模式文件中定义数据模型然后,该数据模型将通过 Prisma Migrate 映射到数据库,后者将生成并发送 SQL 语句以创建与您的数据模型对应的表。

由于您正在构建博客,因此应用程序的主要实体将是usersposts在这一步中,您将定义一个PostPostGraphQL 模式中类型具有相似结构模型在后面的步骤中,您将改进应用程序并添加User模型。

注意: GraphQL API 可以被视为数据库的抽象层。在构建 GraphQL API 时,GraphQL 架构通常与您的数据库架构非常相似。但是,作为一种抽象,这两个模式不一定具有相同的结构,因此您可以控制要通过 API 公开哪些数据。这是因为某些数据可能被视为与 API 层敏感或无关。

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

schema.prisma从位于项目的文件夹中打开您的文件package.json

  • nano prisma/schema.prisma

注意:您可以使用pwd命令从终端验证您所在的文件夹,该命令将输出当前工作目录。此外,使用ls命令列出文件将帮助您浏览文件系统。

向其中添加以下模型定义:

棱镜graphql/prisma/schema.prisma
...
model Post {
  id        Int     @default(autoincrement()) @id
  title     String
  content   String?
  published Boolean @default(false)
}

你要定义一个模式叫做Post拥有多项领域该模型将被映射到一个数据库字段代表各个

这些id字段具有以下字段属性:

  • @default(autoincrement()):这会为列设置自动递增的默认值。
  • @id:这会将列设置为表的主键。

完成后保存并退出文件。

模型就位后,您现在可以使用 Prisma Migrate 在数据库中创建相应的表。这可以通过migrate dev创建迁移文件并运行它们命令来完成

再次打开终端并运行以下命令:

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

这将输出类似于:

Output
PostgreSQL database my-blog created at localhost:5432 Prisma Migrate created and applied the following migration(s) from new schema changes: migrations/ └─ 20201201110111_init/ └─ migration.sql Everything is now in sync.

此命令在您的文件系统上创建一个新迁移,并针对数据库运行它以创建数据库架构。以下是提供给命令的选项的快速概览:

  • --preview-feature:必需,因为 Prisma Migrate 当前处于预览状态。
  • --name "init":指定迁移的名称(将用于命名在您的文件系统上创建的迁移文件夹)。
  • --skip-generate:跳过生成 Prisma Client(这将在下一步中完成)。

您的prisma/migrations目录现在填充了 SQL 迁移文件。这种方法允许您跟踪对数据库架构的更改并在生产中创建相同的数据库架构。

注意:如果您已经将 Prisma Migrate 与my-blog数据库一起使用,并且prisma/migration文件夹中的迁移与数据库架构之间存在不一致,则会提示您使用以下输出重置数据库:

Output
? We need to reset the PostgreSQL database "my-blog" at "localhost:5432". All data will be lost. Do you want to continue? › (y/N)

您可以通过输入ywhich 将重置数据库来解决此问题请注意,这会导致数据库中的所有数据丢失。

您现在已经创建了数据库架构。在下一步中,您将安装 Prisma Client 并在您的 GraphQL 解析器中使用它。

第 8 步 – 在 GraphQL 解析器中使用 Prisma 客户端

Prisma Client 是一个自动生成且类型安全的对象关系映射器 (ORM),您可以使用它以编程方式从 Node.js 应用程序读取和写入数据库中的数据。在此步骤中,您将在项目中安装 Prisma Client。

再次打开终端并安装 Prisma Client npm 包:

  • npm install @prisma/client

注意: Prisma 客户端通过为node_modules文件夹生成基于 Prisma 架构的代码,为您提供丰富的自动完成功能要生成代码,请使用该npx prisma generate命令。这通常在您创建并运行新迁移后完成。但是,在第一次安装时,这不是必需的,因为它会在postinstall挂钩中自动生成

创建数据库和 GraphQL 模式并安装 Prisma 客户端后,您现在将在 GraphQL 解析器中使用 Prisma 客户端来读取和写入数据库中的数据。您将通过替换posts迄今为止用于保存数据数组来完成此操作

首先创建以下文件:

  • nano src/db.js

添加以下内容:

棱镜graphql/src/db.js
const { PrismaClient } = require('@prisma/client')

module.exports = {
  prisma: new PrismaClient(),
}

这将导入 Prisma Client,创建它的一个实例,并导出您将在解析器中使用的实例。

现在保存并关闭src/db.js文件。

接下来,您将prisma实例导入到src/schema.js. 为此,请打开src/schema.js

  • nano src/schema.js

然后prisma./db文件顶部导入

棱镜graphql/src/schema.js
const { prisma } = require('./db')
...

然后删除posts数组:

棱镜graphql/src/schema.js
-const posts = [
-  {
-    id: 1,
-    title: 'Subscribe to GraphQL Weekly for community news ',
-    content: 'https://graphqlweekly.com/',
-    published: true,
-  },
-  {
-    id: 2,
-    title: 'Follow DigitalOcean on Twitter',
-    content: 'https://twitter.com/digitalocean',
-    published: true,
-  },
-  {
-    id: 3,
-    title: 'What is GraphQL?',
-    content: 'GraphQL is a query language for APIs',
-    published: false,
-  },
-]

现在您将更新Query解析器以从数据库中获取已发布的帖子。resolvers.Query使用以下解析器更新对象:

棱镜graphql/src/schema.js
...
const resolvers = {
  Query: {
    feed: (parent, args) => {
      return prisma.post.findMany({
        where: { published: true },
      })
    },
    post: (parent, args) => {
      return prisma.post.findOne({
        where: { id: Number(args.id) },
      })
    },
  },

在这里,您使用了两个 Prisma 客户端查询:

  • findMany: 获取publish字段为 的帖子false
  • findOne:获取id字段等于idGraphQL 参数的单个帖子

请注意,根据GraphQL 规范ID类型的序列化方式与String. 因此您转换为 aNumber因为idPrisma 架构中的 是int.

接下来,您将更新Mutation解析器以保存和更新数据库中的帖子。resolvers.Mutation使用以下解析器更新对象:

棱镜graphql/src/schema.js
const resolvers = {
  ...
  Mutation: {
    createDraft: (parent, args) => {
      return prisma.post.create({
        data: {
          title: args.title,
          content: args.content,
        },
      })
    },
    publish: (parent, args) => {
      return prisma.post.update({
        where: {
          id: Number(args.id),
        },
        data: {
          published: true,
        },
      })
    },
  },
}

您正在使用两个 Prisma 客户端查询:

  • create: 创建Post记录。
  • update:更新与查询参数中匹配的Post记录的已发布字段id

schema.js现在应该如下所示:

棱镜graphql/src/schema.js
const { gql } = require('apollo-server')
const { prisma } = require('./db')

const typeDefs = gql`
  type Post {
    content: String
    id: ID!
    published: Boolean!
    title: String!
  }

  type Query {
    feed: [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createDraft(content: String, title: String!): Post!
    publish(id: ID!): Post
  }
`

const resolvers = {
  Query: {
    feed: (parent, args) => {
      return prisma.post.findMany({
        where: { published: true },
      })
    },
    post: (parent, args) => {
      return prisma.post.findOne({
        where: { id: Number(args.id) },
      })
    },
  },
  Mutation: {
    createDraft: (parent, args) => {
      return prisma.post.create({
        data: {
          title: args.title,
          content: args.content,
        },
      })
    },
    publish: (parent, args) => {
      return prisma.post.update({
        where: {
          id: Number(args.id),
        },
        data: {
          published: true,
        },
      })
    },
  },
}

module.exports = {
  resolvers,
  typeDefs,
}

保存并关闭文件。

现在您已经更新了解析器以使用 Prisma 客户端,启动服务器以使用以下命令测试 GraphQL API 和数据库之间的数据流:

  • npm start

再一次,您将收到以下输出:

Output
Server ready at: http://localhost:8080

在输出地址处打开 GraphQL 游乐场,并使用步骤 3 中的相同查询测试 GraphQL API。

现在,您将提交更改,以便可以将更改部署到 App Platform。

为了避免提交node_modules文件夹和prisma/.env文件,首先创建一个.gitignore文件:

  • nano .gitignore

将以下内容添加到文件中:

棱镜graphql/.gitignore
node_modules
prisma/.env

保存并退出文件。

然后运行以下两个命令来提交更改:

  • git add .
  • git commit -m 'Add Prisma'

接下来,您将在 App Platform 中将 PostgreSQL 数据库添加到您的应用程序。

步骤 9 — 在 App Platform 中创建和迁移 PostgreSQL 数据库

在此步骤中,您将向 App Platform 中的应用程序添加 PostgreSQL 数据库。然后,您将使用 Prisma Migrate 对其运行迁移,以便部署的数据库架构与您的本地数据库相匹配。

首先,转到App Platform 控制台并选择您在第 5 步中创建prisma-graphql项目。

接下来,转到“组件”选项卡。

组件选项卡

单击+ Create Component并选择Database,这将引导您进入配置数据库的页面。

配置您的数据库

选择Dev Database并单击Create and Attach

您将被重定向回组件页面,其中将有一个用于创建数据库的进度条。

创建数据库

创建数据库后,您将从本地计算机对 DigitalOcean 上的生产数据库运行数据库迁移。要运行迁移,您将需要托管数据库的连接字符串。要获取它,请单击组件选项卡中db图标。

数据库信息

Connection Details下拉列表中选择Connection String并复制数据库 URL,它将具有以下结构:

postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require

然后,在终端中运行以下命令并确保设置DATABASE_URL为与复制的相同 URL:

  • DATABASE_URL="postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require" npx prisma migrate deploy --preview-feature

这将使用 Prisma Migrate 针对实时数据库运行迁移。

如果迁移成功,您将收到以下信息:

Output
PostgreSQL database db created at unique_identifier.db.ondigitalocean.com:25060 Prisma Migrate applied the following migration(s): migrations/ └─ 20201201110111_init/ └─ migration.sql

您已成功迁移 DigitalOcean 上的生产数据库,该数据库现在与 Prisma 架构匹配。

现在,您可以通过使用以下命令推送您的 Git 更改来部署您的应用程序:

  • git push

注意: App Platform 将DATABASE_URL在运行时为您的应用程序提供环境变量。Prisma的客户端将使用通过该环境变量env("DATABASE_URL")datasource你的PRISMA模式的块。

这将自动触发构建。如果您打开 App Platform 控制台,您将看到一个Deploying进度条。

部署

部署成功后,您将收到“已部署成功”消息。

您现在已经使用数据库备份了已部署的 GraphQL API。打开Live App,它将带您进入 GraphQL Playground。使用步骤 3 中的相同查询测试 GraphQL API。

在最后一步中,您将通过添加User模型来改进 GraphQL API

第 10 步 – 添加用户模型

您用于博客的 GraphQL API 有一个名为Post. 在此步骤中,您将通过在 Prisma 模式中定义新模型并调整 GraphQL 模式以使用新模型来改进 API。您将介绍一个User与模型具有一对多关系Post模型。这将允许您代表帖子的作者并将多个帖子关联到每个用户。然后,您将改进 GraphQL 架构,以允许通过 API 创建用户并将帖子与用户相关联。

首先,打开 Prisma 架构并添加以下内容:

棱镜graphql/prisma/schema.prisma
...
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  name  String
  posts Post[]
}

您已将以下内容添加到 Prisma 架构中:

  • User代表用户模型。
  • 两个关系字段:authorposts关系字段在 Prisma 级别定义模型之间的连接,并且不存在于数据库中。这些字段用于生成 Prisma 客户端并访问与 Prisma 客户端的关系。
  • authorId字段,其通过引用的@relation属性。Prisma 将在数据库中创建一个外键来连接PostUser

请注意,Post模型中的作者字段是可选的。这意味着您将能够创建与用户无关的帖子。

完成后保存并退出文件。

接下来,使用以下命令在本地创建和应用迁移:

  • npx prisma migrate dev --preview-feature --name "add-user"

如果迁移成功,您将收到以下信息:

Output
Prisma Migrate created and applied the following migration(s) from new schema changes: migrations/ └─ 20201201123056_add_user/ └─ migration.sql ✔ Generated Prisma Client to ./node_modules/@prisma/client in 53ms

该命令还会生成 Prisma 客户端,以便您可以使用新表和字段。

现在,您将对 App Platform 上的生产数据库运行迁移,以便数据库架构与您的本地数据库相同。在终端中运行以下命令并设置DATABASE_URL为来自 App Platform 的连接 URL:

  • DATABASE_URL="postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require" npx prisma migrate deploy --preview-feature

您将收到以下信息:

Output
Prisma Migrate applied the following migration(s): migrations/ └─ 20201201123056_add_user/ └─ migration.sql

您现在将更新 GraphQL 架构和解析器以使用更新后的数据库架构。

打开src/schema.js文件并添加更新typeDefs如下:

棱镜graphql/src/schema.js
...
const typeDefs = gql`
  type User {
    email: String!
    id: ID!
    name: String
    posts: [Post!]!
  }

  type Post {
    content: String
    id: ID!
    published: Boolean!
    title: String!
    author: User
  }

  type Query {
    feed: [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createUser(data: UserCreateInput!): User!
    createDraft(authorEmail: String, content: String, title: String!): Post!
    publish(id: ID!): Post
  }

  input UserCreateInput {
    email: String!
    name: String
    posts: [PostCreateWithoutAuthorInput!]
  }

  input PostCreateWithoutAuthorInput {
    content: String
    published: Boolean
    title: String!
  }
`
...

在此更新后的代码中,您将向 GraphQL 架构添加以下更改:

  • User类型,它返回的数组Post
  • 类型author字段Post
  • createUser突变,预计UserCreateInput作为其输入类型。
  • PostCreateWithoutAuthorInput输入中使用输入类型,用于UserCreateInput创建帖子作为createUser突变的一部分
  • 突变authorEmail可选参数createDraft

更新架构后,您现在将更新解析器以匹配架构。

更新resolvers对象如下:

棱镜graphql/src/schema.js
...
const resolvers = {
  Query: {
    feed: (parent, args) => {
      return prisma.post.findMany({
        where: { published: true },
      })
    },
    post: (parent, args) => {
      return prisma.post.findOne({
        where: { id: Number(args.id) },
      })
    },
  },
  Mutation: {
    createDraft: (parent, args) => {
      return prisma.post.create({
        data: {
          title: args.title,
          content: args.content,
          published: false,
          author: args.authorEmail && {
            connect: { email: args.authorEmail },
          },
        },
      })
    },
    publish: (parent, args) => {
      return prisma.post.update({
        where: { id: Number(args.id) },
        data: {
          published: true,
        },
      })
    },
    createUser: (parent, args) => {
      return prisma.user.create({
        data: {
          email: args.data.email,
          name: args.data.name,
          posts: {
            create: args.data.posts,
          },
        },
      })
    },
  },
  User: {
    posts: (parent, args) => {
      return prisma.user
        .findOne({
          where: { id: parent.id },
        })
        .posts()
    },
  },
  Post: {
    author: (parent, args) => {
      return prisma.post
        .findOne({
          where: { id: parent.id },
        })
        .author()
    },
  },
}

让我们分解对解析器的更改:

  • createDraft突变解析器现在使用的authorEmail参数(如果通过)创建创建草案和现有用户之间的关系。
  • 新的createUser突变解析器使用嵌套写入创建用户和相关帖子
  • The User.posts and Post.author resolvers define how to resolve the posts and author fields when the User or Post are queried. These use Prisma’s Fluent API to fetch the relations.

Save and exit the file.

Start the server to test the GraphQL API:

  • npm start

Begin by testing the createUser resolver with the following GraphQL mutation:

mutation {
  createUser(data: { email: "[email protected]", name: "Natalia" }) {
    email
    id
  }
}

This will create a user.

Next, test the createDraft resolver with the following mutation:

mutation {
  createDraft(
    authorEmail: "[email protected]"
    title: "Deploying a GraphQL API to App Platform"
  ) {
    id
    title
    content
    published
    author {
      id
      name
    }
  }
}

Notice that you can fetch the author whenever the return value of a query is Post. In this example, the Post.author resolver will be called.

Finally, commit your changes and push to deploy the API:

  • git add .
  • git commit -m "add user model"
  • git push

You have successfully evolved your database schema with Prisma Migrate and exposed the new model in your GraphQL API.

Conclusion

In this article, you built a GraphQL API with Prisma and GraphQL, and deployed it to DigitalOcean’s App Platform. You defined a GraphQL schema and resolvers with Apollo Server. You then used Prisma Client in your GraphQL resolvers to persist and query data in the PostgreSQL database.

As a next step, you can further extend the GraphQL API with a query to fetch individual users and a mutation to connect an existing draft to a user.

If you’re interested in exploring the data in the database, check out Prisma Studio. Be sure to visit the Prisma documentation to learn about different aspects of Prisma and explore some ready-to-run example projects in the prisma-examples repository.

You can find the code for this project in the DigitalOcean Community repository.

觉得文章有用?

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