所有文章 > API开发 > 使用GraphQL、Prisma和React实现端到端的类型安全:GraphQL API
使用GraphQL、Prisma和React实现端到端的类型安全:GraphQL API

使用GraphQL、Prisma和React实现端到端的类型安全:GraphQL API

在本系列中,您将学习如何使用 ReactGraphQL、Prisma 和其他一些将这三者联系在一起的有用工具来实现端到端类型安全。

介绍

在本节中,您将通过充实 GraphQL API 来构建在本系列上一篇文章中设置的项目。

在构建此 API 时,您将专注于确保与数据库的交互、解析程序中的数据处理以及数据响应都是类型安全的,并且这些类型是同步的。

启动 GraphQL 服务器

构建 GraphQL API 首先需要的是正在运行的 GraphQL 服务器。在此应用程序中,您将使用 GraphQL Yoga 作为 GraphQL 服务器。

安装 和 包以开始使用:@graphql-yoga/nodegraphql

npm install @graphql-yoga/node graphql

安装这些软件包后,您现在可以启动自己的 GraphQL 服务器。前往 。将现有内容替换为以下代码段:src/index.ts

// src/index.ts

// 1import { createServer } from "@graphql-yoga/node";// 2const port = Number(process.env.API_PORT) || 4000// 3const server = createServer({ port});// 4server.start().then(() => { console.log(`🚀 GraphQL Server ready at http://localhost:${port}/graphql`);});

上面的代码执行以下操作:

  1. 从 GraphQL Yoga 导入函数createServer
  2. 创建一个变量来保存 API 的端口,如果环境中不存在 API,则默认为4000
  3. 创建 GraphQL 服务器的实例
  4. 在端口上启动服务器,并让控制台知道它已启动并正在运行4000

如果您启动服务器,您将可以访问正在运行的(空的)GraphQL API:

npm run dev

注意:GraphQL 服务器已启动并正在运行,但不可用,因为您尚未定义任何查询或更改。

设置 Schema 构建器

GraphQL 使用强类型架构来定义用户如何与 API 交互以及应返回哪些数据。构建 GraphQL 架构有两种不同的方法:代码优先和 SDL 优先。

  • 代码优先:您的应用程序代码定义并生成 GraphQL 架构
  • SDL 优先:您手动编写 GraphQL 架构

在本应用程序中,您将使用名为 Pothos 的流行架构构建器采用代码优先方法。

要开始使用 Pothos,您首先需要安装 core 包:

npm i @pothos/core

接下来,创建 Pothos 架构构建器的实例作为可共享模块。在该文件夹中,创建一个名为 for will hold this module的新文件:srcbuilder.ts

cd src
touch builder.ts

现在,从包中导入默认导出并导出名为 :@pothos/corebuilder

// src/builder.ts

import SchemaBuilder from "@pothos/core";export const builder = new SchemaBuilder({});

定义标量类型DATE

默认情况下,GraphQL 仅支持一组有限的标量数据类型:

  • Int (整数)
  • 字符串
  • 布尔
  • 身份证

但是,如果您回想一下 Prisma 架构,您会记得定义了一些使用数据类型的字段。要在 GraphQL API 中处理这些问题,您需要定义自定义标量类型。DateTimeDate

幸运的是,由于开源社区,可以使用预制的自定义标量类型定义。您将使用的称为graphql-scalars:

npm i graphql-scalars

您需要向架构构建器注册标量,让它知道如何处理日期。架构构建器采用一个通用的,您可以在其中指定各种配置。Date

进行以下更改以注册标量类型:Data

// src/builder.ts

import SchemaBuilder from "@pothos/core";// 1import { DateResolver } from "graphql-scalars";
// 2export const builder = new SchemaBuilder<{ Scalars: { Date: { Input: Date; Output: Date }; };}>({});
// 3builder.addScalarType("Date", DateResolver, {});

以下是上面代码段中的更改内容。你:

  1. 导入标量类型的解析程序,该解析程序处理在 API 中将值转换为正确的日期类型Date
  2. 注册一个名为 using the configuration 的新标量类型,并配置在访问和验证此类型的字段时要使用的 JavaScript 类型"Date"SchemaBuilderScalars
  3. 通过提供导入的DateDateResolver

在 GraphQL 对象类型和解析程序中,现在可以使用标量类型。Date

添加 Pothos Prisma 插件

您需要做的下一件事是定义 GraphQL 对象类型。这些定义 API 将通过查询公开的对象和字段。

Pothos 有一个很棒的 Prisma 插件,它使这个过程更加顺畅,并在 GraphQL 类型和数据库架构之间提供类型安全。

注意:Pothos 可以在 Prisma 中以类型安全的方式使用,而无需使用插件,但是该过程非常手动。

首先,安装插件:

npm i @pothos/plugin-prisma

此插件提供了一个 Prisma 生成器,可生成 Pothos 所需的类型。在以下位置将生成器添加到您的 Prisma 架构中:prisma/schema.prisma

// prisma/schema.prisma
generator client { provider = "prisma-client-js"}
+generator pothos {+ provider = "prisma-pothos-types"+}
datasource db { provider = "postgresql" url = env("DATABASE_URL")}
model User { id Int @id @default(autoincrement()) name String createdAt DateTime @default(now()) messages Message[]}
model Message { id Int @id @default(autoincrement()) body String createdAt DateTime @default(now()) userId Int user User @relation(fields: [userId], references: [id])}

添加后,您将需要一种方法来生成 Pothos 的神器。在本系列的后面部分,每次部署此应用程序时,您都需要安装此 API 的节点模块并重新生成 Prisma Client,因此请继续创建一个新的 in 来处理此问题:scriptpackage.json

// package.json

{ // ... "scripts": { // ... "build": "npm i && npx prisma generate" }}

现在你可以运行该命令来安装你的 node 模块并重新生成 Prisma 客户端和 Pothos 输出:

npm run build

当您运行上述命令时,您应该会看到 Prisma Client 和 Pothos 集成都已生成。

现在这些类型已生成,请前往 。在这里,你将导入和生成的 Pothos 类型,并将它们应用于你的构建器:src/builder.tsPrismaPlugin

// src/builder.ts
import SchemaBuilder from "@pothos/core";import { DateResolver } from "graphql-scalars";+import PrismaPlugin from "@pothos/plugin-prisma";+import type PrismaTypes from "@pothos/plugin-prisma/generated";
export const builder = new SchemaBuilder<{ Scalars: { Date: { Input: Date; Output: Date }; };}>({});
builder.addScalarType("Date", DateResolver, {});

添加生成的类型后,您会注意到 .SchemaBuilder

Pothos 足够聪明,知道因为你使用的是 Prisma 插件,所以你需要向构建器提供一个实例。Pothos 使用它来推断有关 Prisma Client 中的类型的信息。在下一步中,您将创建该实例并将其添加到构建器中。prisma

现在,在 builder 实例中注册 Prisma 插件和生成的类型,让 Pothos 知道它们:

// src/builder.ts// ...
export const builder = new SchemaBuilder<{ Scalars: { Date: { Input: Date; Output: Date }; };+ PrismaTypes: PrismaTypes;}>({+ plugins: [PrismaPlugin],});
// ...

此时,您将再次看到 TypeScript 错误。这是因为现在需要为函数提供 Prisma Client 的实例。builder

在下一步中,您将实例化 Prisma 客户端,并在 .builder

创建 Prisma Client 的可重用实例

您现在需要创建一个可重用的 Prisma Client 实例,该实例将用于查询您的数据库,并提供上一步中构建器所需的类型。

在名为 的文件夹中创建一个新文件。srcdb.ts

touch src/db.ts

在该文件中,导入 Prisma Client 并创建名为 的客户端实例。导出该实例化的客户端:prisma

// src/db.ts

import { PrismaClient } from "@prisma/client";export const prisma = new PrismaClient();

将变量导入并提供它以摆脱 TypeScript 错误:prismasrc/builder.tsbuilder

// src/builder.ts
// ...
+import { prisma } from "./db";
export const builder = new SchemaBuilder<{ Scalars: { Date: { Input: Date; Output: Date }; }; PrismaTypes: PrismaTypes;}>({ plugins: [PrismaPlugin],+ prisma: {+ client: prisma,+ },});
// ...

Pothos Prisma 插件现已完全配置并准备就绪。这采用 Prisma 生成的类型,并允许您在 GraphQL 对象类型和查询中轻松访问这些类型。

最酷的是,您现在有一个单一的事实来源(Prisma 架构)来处理数据库中的类型、用于查询数据库的 API 以及 GraphQL 架构。

接下来,您将看到它的实际效果!

定义 GraphQL 类型

此时,您将使用通过 Prisma 插件配置的构建器定义 GraphQL 对象类型。

注意:如果您已经在 Prisma 架构中定义了数据的形状,那么手动定义 GraphQL 对象类型似乎是多余的。Prisma 架构定义数据库中数据的形状,而 GraphQL 架构定义 API 中可用的数据。

在 中创建一个名为 的新文件夹。然后在该新文件夹中创建一个文件:srcmodelsUser.ts

mkdir src/models
touch src/models/User.ts

在这里,您将定义将通过 GraphQL API 公开的对象类型及其相关查询。导入实例:Userbuilder

// src/models/User.ts

import { builder } from "../builder";

由于您使用的是 Pothos 的 Prisma 插件,因此该实例现在有一个名为builderprismaObject您将用于定义对象类型。

该方法采用两个参数:

  1. name:此新类型表示的 Prisma 模型的名称
  2. options:正在定义的类型的配置

使用该方法创建类型:"User"

// src/models/User.ts
import { builder } from "../builder";
+builder.prismaObject("User", {})

注意:如果您在字段中输入之前在一组空引号中按 +,您应该会获得一些不错的自动完成功能,其中包含 Prisma 架构中的可用模型列表,这要归功于 Prisma 插件。CtrlSpacename

在对象中,添加一个使用 Pothos 的 “expose” 函数定义 和 字段的键:optionsfieldsidnamemessages

// src/models/User.ts

import { builder } from "../builder";
builder.prismaObject("User", { fields: t => ({ id: t.exposeID("id"), name: t.exposeString("name"), messages: t.relation("messages") })})

注意:当您开始键入字段名称时,按 + 将为您提供目标模型中与您正在使用的 “expose” 函数的数据类型匹配的字段列表。CtrlSpace

上面的函数定义了一个 GraphQL 类型定义,并在实例中注册了它。从 生成架构实际上并不会将 GraphQL 架构存储在您可以查看的文件系统中,但是生成的类型定义将如下所示:builderbuilderUser

type User {
id: ID! messages: [Message!]! name: String!}

接下来,在同一文件夹中添加另一个名为 :Message.ts

touch Message.ts

此文件与文件类似,不同之处在于它将定义模型。User.tsMessage

定义 、 和 字段。请注意,该字段在您的 Prisma 架构中具有类型,并且需要一个自定义配置来定义您定义的自定义标量类型:idbodycreatedAtcreatedAtDateTimedate

// src/models/Message.ts

import { builder } from "../builder";
builder.prismaObject("Message", { fields: (t) => ({ id: t.exposeID("id"), body: t.exposeString("body"), createdAt: t.expose("createdAt", { type: "Date", }), }),});

此函数将生成以下 GraphQL 对象类型:

type Message {
body: String! createdAt: Date! id: ID!}

实施您的查询

目前,您已为 GraphQL 架构定义了对象类型,但您尚未定义实际访问该数据的方法。为此,您首先需要初始化一个Query类型.

在文件底部,使用 的 函数初始化类型:src/builder.tsQuerybuilderqueryType

// src/builder.ts

// ...
builder.queryType({});

这将注册一个特殊的 GraphQL 类型,该类型包含每个查询的定义,并充当 GraphQL API 的入口点。您可以在文件中定义此类型,以确保查询生成器定义了类型,这样您以后就可以向其添加查询字段。builder.tsQuery

在此函数中,您可以直接添加查询定义,但是,您将在代码库中单独定义这些定义,以便更好地组织代码。queryType

将实例导入到 :prismasrc/models/User.ts

// src/models/User.ts
import { builder } from "../builder";+import { prisma } from "../db";
// ...

然后,使用 的builderqueryField函数中,定义一个公开您定义的对象类型的查询:"users"User

// src/models/User.ts
// ...
// 1builder.queryField("users", (t) => // 2 t.prismaField({ // 3 type: ["User"], // 4 resolve: async (query, root, args, ctx, info) => { return prisma.user.findMany({ ...query }); }, }));

上面的代码段:

  1. 向 GraphQL 架构的类型添加一个名为Query"users"
  2. 定义一个字段,该字段解析为 Prisma 架构中的某种类型
  3. 让 Pothos 知道此字段将解析为您的 Prisma Client 类型的数组User
  4. 为此字段设置解析程序功能。

注意:函数的参数位于参数列表的开头。这是 Pothos 在使用用于以高性能方式加载数据和关系的函数时填充的特定字段。如果您来自 GraphQL 背景,这可能会造成混淆,因为它会更改参数的预期顺序。resolvequeryprismaField

为了更好地可视化所发生的事情,以下是本节中的代码将生成的类型和查询:Queryusers

type Query {
users: [User!]!}

应用 GraphQL 架构

现在,您已经定义并实施了所有 GraphQL 对象类型和查询。需要的最后一部分是将所有这些类型和查询注册到一个位置,并根据您的配置生成 GraphQL 架构。

在 中创建一个名为 的新文件 。srcschema.ts

touch schema.ts

此文件将简单地导入模型,导致文件中的代码运行,并运行实例的函数以生成 GraphQL 架构:buildertoSchema

// src/schema.ts

import { builder } from "./builder";
import "./models/Message";import "./models/User";
export const schema = builder.toSchema({});

该函数生成 GraphQL 架构的抽象语法树 (AST) 表示形式。下面,您可以看到 AST 和 GraphQL 表示形式的样子:toSchemaGraphQLAST

scalar Date

type Message { body: String! createdAt: Date! id: ID!}
type Query { users: [User!]!}
type User { id: ID! messages: [Message!]! name: String!}

在您的文件中,导入您刚刚创建的变量。该函数的配置对象采用一个名为 key 的 key,该 key 将接受生成的 GraphQL 架构:src/index.tsschemacreateServerschema

// src/index.ts
import { createServer } from "@graphql-yoga/node";+import { schema } from "./schema";
const port = Number(process.env.API_PORT) || 4000
const server = createServer({ port,+ schema,});
server.start().then(() => { console.log(`🚀 GraphQL Server ready at http://localhost:${port}/graphql`);});

匪夷所思!您的 GraphQL 架构是使用代码优先方法定义的,您的 GraphQL 对象和查询类型与您的 Prisma 架构模型同步,并且您的 GraphQL 服务器正在获得生成的 GraphQL 架构。

此时,请运行服务器,以便您可以使用 API:

npm run dev

运行上述命令后,在浏览器中打开 http://localhost:4000/graphql 以访问 GraphQL Playground。您应该会看到一个如下所示的页面:

在屏幕的左上角,点击 Explorer 按钮以查看 API 的可用查询和更改:

如果您单击 users 查询类型,屏幕右侧将自动填充对用户数据的查询。

通过点击 “execute query” 按钮来运行该查询,以查看 API 的运行情况:

随意使用不同的选项来选择要查询的字段以及要包含的 “messages” 关系中的哪些数据。

总结和下一步是什么

在本文中,您构建了整个 GraphQL API。该 API 是利用 Prisma 生成的类型以类型安全的方式构建的。这些与 Pothos Prisma 插件一起,使您能够确保 ORM 中的类型、GraphQL 对象类型、GraphQL 查询类型和解析程序都与数据库架构同步。

在此过程中,您需要:

  • 使用 GraphQL Yoga 设置 GraphQL 服务器
  • 设置 Pothos 架构构建器
  • 定义 GraphQL 对象和查询类型
  • 使用 Prisma Client 查询数据

原文来源:https://www.prisma.io/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg

#你可能也喜欢这些API文章!