将GraphQL Nexus与数据库一起使用

将GraphQL Nexus与数据库一起使用

翻译:hanagm

原文链接

GraphQL Nexus是JavaScript / TypeScript的代码优先、类型安全的GraphQL架构构建库。了解如何使用Prisma客户端和新nexus-prisma插件将其连接到数据库。

img

本文是由三部分组成的系列文章的第三部分:

上一篇文章中,我们介绍了GraphQL Nexus,这是一个GraphQL库,可以为TypeScript和JavaScript实现代码优先开发。使用Nexus,GraphQL架构以编程方式定义和实现。因此,它遵循GraphQL服务器在其他语言中的成熟方法,例如sangria-graphql(Scala)graphlq-rubygraphene(Python)。

今天的文章是关于使用Prisma client和新nexus-prisma插件,将基于Nexus的GraphQL服务器连接到数据库。我们稍后将向您介绍从头开始为博客应用程序构建GraphQL API的实际示例。

nexus-prisma适用于PostgreSQL,MySQL和MongoDB。在这里找到它的文档。

  • GraphQL中Prisma模型的CRUD操作

  • 自定义Prisma模型,例如隐藏某些字段添加计算字段

  • 完全类型安全:GraphQL模式和数据库的一致类型集

  • 与GraphQL生态系统兼容(例如apollo-servergraphql-yoga,...)

如果您以前没有使用过Prisma,请快速了解它的工作原理:

  1. 定义您的数据模型或让Prisma内省您现有的数据库
  2. 生成您的Prisma客户端,即类型安全的数据库客户端
  3. 使用Prisma客户端访问应用程序中的数据库(例如GraphQL API)

添加nexus-prisma时,还有另一个步骤:调用nexus-prisma-generatecodegen CLI。它为您的Prisma模型生成完整的GraphQL CRUD API的构建块,例如,User它包括以下模型:

  • Queries

    • user(...): User!: 获取单个记录
    • users(...): [User!]!: 获取记录列表
    • usersConnection(...): UserConnection!: Relay connections & aggregations
  • Mutations

    • createUser(...): User!: 创建新记录
    • updateUser(...): User: 更新记录
    • deleteUser(...): User: 删除记录
    • updatesManyUsers(...): BatchPayload!: 批量更新多条记录
    • deleteManyUsers(...): BatchPayload!: 批量删除多条记录
  • GraphQL input types

    • UserCreateInput:覆盖记录的所有字段
    • UserUpdateInput:覆盖记录的所有字段
    • UserWhereInput:为记录的所有字段提供过滤器
    • UserWhereUniqueInput:为记录的唯一字段提供过滤器
    • UserUpdateManyMutationInput:覆盖可以批量更新的字段
    • UserOrderByInput:按字段指定升序或降序

UserCreateInput并且UserUpdateInput关系字段的处理方式不同。

当编写GraphQL服务器代码nexusnexus-prisma时,暴露和定制你自己的API需要这些操作:

img

生成CRUD构建块后,您可以使用prismaObjectTypefrom nexus-prisma开始公开(和自定义)它们。

以下代码片段描述了一个实现,该实现为基于Prisma的TODO列表应用程序提供GraphQL API,并且nexus-prisma

  1. Prisma数据模型
type Todo {
  id: ID! @unique
  title: String!
  done: Boolean! @default(value: "false")
}
  1. GraphQL服务器(with nexus-prisma)
import { prismaObjectType } from 'nexus-prisma'
import { idArg } from 'nexus-prisma'

// Expose the full "Query" building block
const Query = prismaObjectType({ 
  name: 'Query',
  definition: t => t.prismaFields(['*'])
})

// Customize the "Mutation" building block
const Mutation = prismaObjectType({ 
  name: 'Mutation',
  definition(t) {
    // Keep only the `createTodo` mutation
    t.prismaFields(['createTodo'])

    // Add a custom `markAsDone` mutation
    t.field('markAsDone', {
      args: { id: idArg() },
      nullable: true,
      resolve: (_, { id }, ctx) {
        return ctx.prisma.updateTodo({
          where: { id },
          data: { done: true }
        })
      }
    })
  }
})

const schema = makePrismaSchema({
  types: [Query, Mutation],

  // More config stuff, e.g. where to put the generated SDL
})

// Feed the `schema` into your GraphQL server, e.g. `apollo-server, `graphql-yoga` 
  1. GraphQL API(Generated SDL)
# The fully exposed "Query" building block
type Query {
  todo(where: TodoWhereUniqueInput!): Todo
  todoes(after: String, before: String, first: Int, last: Int, orderBy: TodoOrderByInput, skip: Int, where: TodoWhereInput): [Todo!]!
  todoesConnection(after: String, before: String, first: Int, last: Int, orderBy: TodoOrderByInput, skip: Int, where: TodoWhereInput): TodoConnection!
}

# The customized "Mutation" building block
type Mutation {
  createTodo(data: TodoCreateInput!): Todo!
  markAsDone(id: ID): Todo
}

# The Prisma model
type Todo {
  done: Boolean!
  id: ID!
  title: String!
}

# More of the generated building blocks:
# e.g. `TodoWhereUniqueInput`, `TodoCreateInput`, `TodoConnection`, ...

我们将prismaObjectTypeQueryMutation。对于Query,我们保持所有领域(即todotodoestodoesConnection)。因为Mutation我们正在使用prismaFields自定义公开的操作。

prismaFields让我们选择要公开的操作。在这种情况下,我们只想保持操作以创建模型(createTodo)。从生成的CRUD构建块中,我们既不包括updateTodo也不包含deleteTodo但是实现我们自己的markAsDone(id: ID!)突变来检查某个特定的突变。


现在让我们快速浏览一下标准的Prisma用例,看看如何通过几个简单的步骤快速为博客应用程序构建GraphQL API。这是我们要做的:

  1. 使用TypeScript设置Prisma项目(使用免费的演示数据库)
  2. 定义模型,迁移数据库并生成Prisma客户端
  3. 通过公开完整的CRUD GraphQL API nexus-prisma
  4. 通过自定义GraphQL API nexus-prisma

如果您想跟随,则需要安装Prisma CLI:

npm(国内推荐使用cnpm)

npm install -g prisma
#or
cnpm install -g prisma

或yarn

yarn global add prisma

本节主要介绍您的项目设置。如果您不想编码,请随意跳过它到第二步,否则请参阅项目设置说明

请参阅项目设置说明

​ 使用Prisma CLI创建一个简单的Prisma项目:

prisma init myblog

在交互式提示中,选择以下选项:

1、选择demo server(拥有Prisma Cloud中的免费和托管demo数据库)

2、在浏览器中使用Prisma Cloud进行身份验证(如有必要)

3、返回终端,确认所有建议值

作为Demo服务器的替代方案,您还可以使用Docker在本地运行Prisma

接下来,您需要配置nexus-prisma工作流程。添加以下依赖项(在myblog目录中):

npm init -y
npm install --save nexus graphql nexus-prisma prisma-client-lib graphql-yoga
npm install --save-dev typescript ts-node-dev

接下来,在prisma.yml的末尾添加以下两行:

hooks:
  post-deploy:
    - prisma generate
    - npx nexus-prisma-generate --client ./generated/prisma-client --output ./generated/nexus-prisma

这样可以确保每当您对模型进行更改时,prisma client以及生成的nexus prisma crud构建块都会被更新。既然我们使用的是typescript,那么让我们快速添加一个tsconfig.json

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext", "dom"]
  }
}

最后,继续添加一个用于开发的开始脚本。它启动一个开发服务器,在后台监视您的文件,并在编码时更新生成的SDL和Nexus类型。将其添加到package.json中:

"scripts": {
  "start": "ts-node-dev --no-notify --respawn --transpileOnly ./"
},

prisma init命令在中创建了默认User模型datamodel.prisma。在我们构建博客应用程序时,让我们将模型调整到我们的应用程序域:

type User {
  id: ID! @unique
  email: String! @unique
  name: String
  posts: [Post!]!
}

type Post {
  id: ID! @unique
  createdAt: DateTime!
  updatedAt: DateTime!
  published: Boolean! @default(value: "false")
  title: String!
  content: String
  author: User!
}

接下来,您需要通过将数据模型应用于数据库来迁移数据库。使用以下命令,定义的每个模型datamodel.prisma将映射到基础数据库中的表:

prisma deploy

请注意,Prisma很快将拥有更强大的迁移系统。了解更多

由于您之前配置了post-deploy挂钩prisma.yml,因此您的Prisma客户端和CRUD构建块会自动更新。

在项目的早期阶段,通过API公开完整的CRUD功能通常很有帮助 - 通常会出现更多受限制的API要求。nexus-prisma通过提供从完整CRUD到自定义API操作的简单路径,完美地解决了这个问题。

让我们从一个GraphQL API开始,它为已定义的模型公开完整的CRUD(请注意,这包括过滤器,分页和排序)。创建一个名为的新文件index.ts,并将以下代码添加到其中:

import * as path from 'path'
import { GraphQLServer } from 'graphql-yoga'
import { makePrismaSchema, prismaObjectType } from 'nexus-prisma'
import { prisma } from './generated/prisma-client'
import datamodelInfo from './generated/nexus-prisma'

const Query = prismaObjectType({ 
  name: 'Query',
  definition: t => t.prismaFields(['*'])
})
const Mutation = prismaObjectType({ 
  name: 'Mutation',
  definition: t => t.prismaFields(['*'])
})

const schema = makePrismaSchema({
  types: [Query, Mutation],

  prisma: {
    datamodelInfo,
    client: prisma
  },

  outputs: {
    schema: path.join(__dirname, './generated/schema.graphql'),
    typegen: path.join(__dirname, './generated/nexus.ts'),
  },
})

const server = new GraphQLServer({
  schema,
  context: { prisma }
})
server.start(() => console.log(`Server is running on http://localhost:4000`))

在这个简短的教程中,我们没有太多关注文件结构。检查我们的graphql-auth示例以获得正确的设置和模块化架构。

运行后npm run start,您可以在http://localhost:4000上为您的graphql服务器打开graphql操作界面。

使用上面编写的小代码,您已经拥有了一个完整的GraphQL CRUD API。

查看一些queries 和 mutations 示例

# Fetch all posts with their authors
query {
  posts {
    id
    title
    published
    author {
      id
      name
    }
  }
}
# Fetch a certain user by email
query {
  user(where: { email: "alice@prisma.io" }) {
    id
    name
    posts {
      id
      title
    }
  }
}
# Create a post and its author
mutation {
  createPost(data: {
    title: "Hello World"
    author: {
      create: {
        email: "bob@prisma.io"
      }
    }
  }) {
    id
    published
    author {
      id
      name
    }
  }
}
# Update the name of a user
mutation {
  updateUser(
    data: { name: "Alice" }
    where: { email: "alice@prisma.io "}
  ) {
    id
    name
  }
}
# Delete a user
mutation {
  deleteUser(where: { email: "alice@prisma.io "}) {
    id
    name
  }
}

这是如何运作的?nexus-prisma-generate生成了一个GraphQL模式,为Prisma模型(您的CRUD构建块)提供CRUD API 。此GraphQL架构遵循OpenCRUD规范。使用该prismaObjectType功能,您现在可以公开和自定义该架构的操作。

prismaObjectTypeprismaFields使用白名单方法,这意味着您需要明确列出要公开的字段。通配符运算符*包括所有字段。

img

在本节中,我们将了解如何nexus-prisma自定义CRUD GraphQL API 。具体来说,我们将:

  1. 隐藏User模型中的字段

  2. 将计算字段添加到Post模型中

  3. 隐藏createPostupdatePost突变

  4. 添加两个自定义createDraftpublish突变

在本节中,我们将隐藏模型中的email字段User

要自定义模型,我们需要将prismaObjectType函数应用于它并将该definition(t)函数作为选项传递:

const User = prismaObjectType({
  name: 'User',
  definition(t) {
    t.prismaFields(['id', 'name', 'posts'])
  }
})

通过调用prismaFields模型t,我们可以自定义公开的字段(及其参数)。因为email未包含在列表中,所以它已从我们的GraphQL API中删除。

img

要应用更改,您需要显式传递User到以下types内部的数组makePrismaSchema

const schema =  makePrismaSchema ({ 
  types : [ Query , Mutation , User ]// ... 
}

请注意,您的编辑器能够根据生成的CRUD构建基块建议传递到prismaObjectType和prismaFields中的内容。这意味着当您键入prismaObjectType('')并点击ctrl+space时,它显示了所有生成的CRUD构建块的名称。当调用t.prismafields([''])时,它表示t的字段:

Jietu20190308-123954-HD

新的代码优先方法nexus-prisma也使得向Prisma模型添加计算字段变得容易。假设你要添加一个字段Post,总是返回title完全大写的拼写。以下是实现代码:

const Post = prismaObjectType({
  name: 'Post',
  definition(t) {
    t.prismaFields(['*'])
    t.string('uppercaseTitle', {
      resolve: ({ title }, args, ctx) => title.toUpperCase()
    })
  }
})

我们使用t.string(...)来自的API 为我们的模型添加一个新字段graphql-nexus。因为它是一个计算字段(因此不能自动解析nexus-prisma),我们还需要附加一个解析器。

img

和以前一样,您需要将自定义Post模型显式添加到types数组中:

const schema = makePrismaSchema({
  types: [Query, Mutation, User, Post], 

  // ...
}

与我们emailUser模型中隐藏字段的方式相同,我们还可以隐藏作为生成的CRUD构建块一部分的Query/ Mutation类型的操作nexus-prisma

要隐藏createPostupdatePost我们再次需要传递definition(t)prismaObjectType选项。请注意,一旦我们调用prismaFields了一个类型,我们需要显式列出我们想要保留的所有字段(空数组将被解释为“保持无操作”):

const Mutation = prismaObjectType({
  name: 'Mutation',
  definition(t) {
    t.prismaFields([
      'createUser',
      'updateUser',
      'deleteUser',
      'deletePost'
    ])
  }
})

生成的CRUD GraphQL API还包括批处理和upsert操作,为简洁起见,我们在此处将其排除。

最后,我们在GraphQL API中添加了两个自定义突变。这是他们的SDL定义应该是什么样子:

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

要实现这些Mutation,您需要将两个字段(通过t.field( ... )nexusAPI 调用)添加到以下Mutation类型:

const Mutation = prismaObjectType({
  name: 'Mutation',
  definition(t) {
    t.prismaFields([
      'createUser',
      'updateUser',
      'deleteUser',
      'deletePost',
    ])
    t.field('createDraft', {
      type: 'Post',
      args: {
        title: stringArg(),
        content: stringArg({ nullable: true })
      },
      resolve: (parent, { title, content }, ctx) => {
        return ctx.prisma.createPost({ title, content })
      }
    })
    t.field('publish', {
      type: 'Post',
      nullable: true,
      args: {
        id: idArg(),
      },
      resolve: (parent, { id }, ctx) => {
        return ctx.prisma.updatePost({ 
          where: { id },
          data: { published: true },
         })
      }
    })
  }
})

一定要导入stringArgidArgnexus包中进行此操作。

GraphQL Nexus还会生成GraphQL架构的SDL版本,您可以在其中找到它./generated/schema.graphql。我们的GraphQL API的最终版本如下所示:

type Mutation {
  createDraft(content: String, title: String): Post!
  createUser(data: UserCreateInput!): User!
  deletePost(where: PostWhereUniqueInput!): Post
  deleteUser(where: UserWhereUniqueInput!): User
  publish(id: ID): Post
  updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
}

type Query {
  node(id: ID!): Node
  post(where: PostWhereUniqueInput!): Post
  posts(after: String, before: String, first: Int, last: Int, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): [Post!]!
  postsConnection(after: String, before: String, first: Int, last: Int, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): PostConnection!
  user(where: UserWhereUniqueInput!): User
  users(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]!
  usersConnection(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): UserConnection!
}

type User {
  id: ID!
  name: String
  posts(after: String, before: String, first: Int, last: Int, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): [Post!]
}

type Post {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime!
  author: User!
  content: String
  published: Boolean!
  title: String!
  uppercaseTitle: String!
}

# More generated SDL types ...

这是我们关于代码优先的GraphQL服务器开发的系列文章的最后一部分。

GraphQL Nexusnexus-prisma插件实现了我们从GraphQL生态系统的积极贡献者那里收集的知识,已有两年多了。在使用SDL优先方法发现太多问题之后,我们对目前正在出现的新的代码优先工具感到非常兴奋。

我们坚信GraphQL Nexus(以及其他代码优先的方法,如TypeGraphQL)将彻底改变未来构建GraphQL模式的方式。

以下是该系列的主要搭配:

  1. 代码优先方法允许您以语言惯用的方式构建GraphQL服务器,而无需额外的工具(“您需要的唯一工具是您的编程语言”),同时保留SDL作为通信工具的优势。
  2. GraphQL Nexus允许开发人员使用灵活且类型安全的API构建其模式。由于自动完成和构建时错误检查,开发人员获得了惊人的体验。
  3. nexus-prisma插件构建于Prisma模型之上,允许开发人员通过公开和自定义自动生成的CRUD构建块来构建GraphQL API。

有几种方法可供您试用nexus-prisma。您可以按照文档中的“ 入门部分进行操作,也可以浏览我们的TypeScript GraphQL示例

请通过打开GitHub问题或联系我们的Slack来分享您的反馈。


向我们的开源工程师Flavian Desverne致敬,感谢他对nexus-prisma插件所做的出色工作

返回