In this article, we’ll be adding relations between object types and looking at some basic schema definition language syntax.

在本文中,我们将添加对象类型之间的关系,并研究一些基本的模式定义语言语法。

初始点 ( Starting Point )

We’re building on the basic GraphQL server that was set up in the previous article in this series . If you just want the code for this download it from here or:

我们建立 在本系列上一篇文章中设置 基本GraphQL服务器上 。 如果您只想要此代码,请 从此处下载 或:

git clone --branch setup https://github.com/bjdixon/graphql-server.git

If you’re just cloning or downloading at this point you’ll need to create a MongoDB instance and replace the connection string in the ./index.js file.

如果此时您只是克隆或下载,则需要创建一个MongoDB实例并替换 ./index.js 文件中的连接字符串。

Make sure the dependencies are installed:

确保已安装依赖项:

npm install

Then start the server:

然后启动服务器:

npm start

We’re ready to dive in!

我们准备潜水了!

我们正在建设什么 ( What We’re Building )

Right now we have two models that we can interact with — we’re able to create and list authors and books. Those models have a natural relationship that we would like to define and we should also be able to view individual authors and books, along with their related objects.

现在,我们有两个模型可以与之交互-我们能够创建并列出作者和书籍。 这些模型具有我们要定义的自然关系,我们还应该能够查看各个作者和书籍及其相关对象。

扩展架构 ( Extending the Schema )

So far in our ./schema/index.js file we have defined two object types, Author and Book using the Schema Definition Language (SDL).

到目前为止,在我们的 ./schema/index.js 文件中,我们已经使用模式定义语言(SDL)定义了两种对象类型,即 Author Book

type Author {
id: ID!
name: String!
}
type Book {
id: ID!
name: String!
pages: Int
}

Each object type has a list of properties and an associated type for each property. In our example we have a Book type that has three properties:

每个对象类型都有一个属性列表以及每个属性的关联类型。 在我们的示例中,我们的 Book 类型具有三个属性:

  • id which is a special ID type.

    id 是一种特殊的ID类型。

  • name which is a string type.

    name 是字符串类型。

  • pages which is an int type.

    pages 是int类型。

A list of all supported types can be found here .

可以在此处找到 所有受支持类型的列表。

You may notice the ! at the end of some of the type definitions. This indicates that the property is required. A good illustration of this can be seen in our Query type:

您可能会注意到 ! 在某些类型定义的末尾。 这表明该属性是必需的。 我们的 Query 类型可以很好地说明这一点:

type Query {
message: String!
authors: [Author!]!
books: [Book!]!
}

The message property requires a string, the authors property requires a list (lists are indicated by using square brackets) and the values in that list are required to be Author types, although returning an empty list would also be valid. For now, we want the authors property to be either null or a list and to make sure that if it does return a non-empty list then it only contains Author type data. Let’s also get rid of the static message query, we won’t be using it again:

message 属性需要一个字符串, authors 属性需要一个列表(列表使用方括号表示),并且该列表中的值必须是 Author 类型,尽管返回一个空列表也是有效的。 现在,我们希望 authors 属性为null或列表,并确保如果它确实返回非空列表,则仅包含 Author 类型数据。 让我们也摆脱静态消息查询,我们将不再使用它:

type Query {
authors: [Author!]
books: [Book!]!
}

You can also delete the message resolver. Just delete the following line:

您也可以删除邮件解析器。 只需删除以下行:

message: () => 'hello world',

Apart from the message deletion, the difference to our queries is subtle. The author’s line no longer terminates with an exclamation mark. In the example above the authors property can return null or a list, but if it returns a non-empty list, that list can only contain authors. The books property must return a list (which can be empty) and that list may only contain books.

除了删除邮件外,我们查询的区别也是微妙的。 作者的行不再以感叹号结尾。 在上面的示例中, authors 属性可以返回null或列表,但是如果它返回非空列表,则该列表只能包含authors。 books 属性必须返回一个列表(可以为空),并且该列表只能包含书籍。

With this knowledge we’ll create a couple of relations in our schema. To our Book type we will add a required author property (we’ll assume only one person can be an author of a book) and that value must be an Author type. To our Author type we will add a books property that can be null or a list and if that list is not empty the values must be Book types. Edit the following in ./schema/index.js :

有了这些知识,我们将在架构中创建几个关系。 在我们的 Book 类型中,我们将添加一个必填的author属性(假设只有一个人可以是一本书的作者),并且该值必须是 Author 类型。 在我们的 Author 类型中,我们将添加一个 books 属性,该属性可以为null或一个列表,如果该列表不为空,则值必须为 Book 类型。 在 ./schema/index.js 编辑以下 ./schema/index.js

type Author {
id: ID!
name: String!
books: [Book!]
}
type Book {
id: ID!
name: String!
pages: Int
author: Author!
}

We can still list authors and return their names, but we’ll only be able to get null for their books. If we try to list books and return the authors of those books, we’ll get an error — books are now required to have an author of type Author and our book instances don’t have any authors yet.

我们仍然可以列出作者并返回他们的名字,但是我们只能使他们的书为空。 如果我们尝试列出书籍并返回这些书籍的作者,则会出现错误-现在要求书籍的作者类型为 Author 而我们的图书实例还没有任何作者。

We still have to update the underlying models and the mutations that create our books and authors.

我们仍然必须更新基础模型以及创建我们的书籍和作者的变异。

扩展模型 ( Extend the Models )

We’ll extend the Author model first, by adding the books array. We’re actually just going to store a list of Mongo ObjectId s (as you’ll be able to see in the type property for books) and use the ref property to let Mongo know which model we want this to be related to. Edit ./models/Author.js :

首先,通过添加books数组来扩展 Author 模型。 实际上,我们只是要存储Mongo ObjectId 的列表(您将能够在book的 type 属性中看到),并使用ref属性让Mongo知道我们希望与之关联的模型。 编辑 ./models/Author.js

import mongoose from 'mongoose'const Schema = mongoose.Schemaexport const Author = mongoose.model('Author', {
name: String,
books: [{
type: Schema.Types.ObjectId,
ref: 'Book'
}]
})

Similarly, we need to update the Book model but instead of an array of authors we’re adding a relation to a single Author. Edit this file ./models/Book.js :

同样,我们需要更新 Book 模型,但要添加一个与单个Author的关系而不是Authors数组。 编辑此文件 ./models/Book.js

import mongoose from 'mongoose'const Schema = mongoose.Schemaexport const Book = mongoose.model('Book', {
name: String,
pages: Number,
author: {
type: Schema.Types.ObjectId,
ref: 'Author'
}
})

Now we can change three lines in our schema and we’ll be able to create our first relations.

现在,我们可以更改架构中的三行,并且可以创建第一个关系。

First, in our Mutation type, we add an author when creating a new book. This will be a string, as Mongo is storing the ID of the author and will accept this as a string. In ./schema/index.js edit createBook in the Mutation type adding the new Author parameter:

首先,在我们的 Mutation 类型中,我们在创建新书时会添加一个作者。 这将是一个字符串,因为Mongo将存储作者的ID,并将其作为字符串接受。 在 ./schema/index.js ,在 Mutation 类型中编辑 createBook ,添加新的 Author 参数:

  type Mutation {
createAuthor(name: String!): Author!
createBook(name: String!, pages: Int, author: String!): Book!
}

Then, in the same file, we’ll make a small change to the createBook resolver adding the author parameter to the function and as an argument to the Book constructor:

然后,在同一个文件中,我们对 createBook 解析器进行少量更改,将author参数添加到函数中,并作为 Book 构造函数的参数:

const resolvers = {
Query: {
authors: () => Author.find(),
books: () => Book.find()
},
Mutation: {
createAuthor: async (_, { name }) => {
const author = new Author({ name });
await author.save();
return author;
},
createBook: async (_, { name, pages, author }) => {
const book = new Book({ name, pages, author });
await book.save();
return book;
}
}
}

测试更新的模型和突变 ( Test the Updated Models and Mutations )

Delete any old Book and Author instances in your MongoDB — they no longer reflect the updated schema. Then create a new Author . Run a query on the Author to get its ID — we’ll need this when creating a Book .

删除MongoDB中的所有旧 Book Author 实例-它们不再反映更新后的架构。 然后创建一个新的 Author 。 在 Author 上运行查询以获取其ID-创建 Book 时将需要此ID。

As you can see, we created the new book without any errors. If we look in the Mongo database we can see that the book instance has an Author property with the ObjectId of our author.

如您所见,我们创建了新书,没有任何错误。 如果我们查看Mongo数据库,则可以看到该图书实例具有 Author 属性,该属性具有 Author ObjectId

Unfortunately though, if we try to get the author details for the new book using our books query we’ll run into trouble.

但是,不幸的是,如果我们尝试使用图书查询来获取新书的作者详细信息,则会遇到麻烦。

解析器中的连接关系 ( Connecting Relations in the Resolvers )

This part is fairly involved but quite exciting and we’ll end up using recursion.

这部分相当复杂,但非常令人兴奋,我们将最终使用递归。

As we know in MongoDB, we’re storing each book’s author property as an ObjectId string and each author’s books array is a list of Book ObjectId s. Let’s turn these ObjectId s into something more useful.

正如我们在MongoDB中所知,我们将每本书的author属性存储为 ObjectId 字符串,而每位作者的books数组都是 Book ObjectId 的列表。 让我们将这些 ObjectId 变成更有用的东西。

We’ll create two helper functions that will be used to expand a Book’s author ObjectId into an Author object and an Author ’s array of book ObjectId s into an array of Book objects. I define these functions in the ./schema/index.js file, just before the resolvers they’re going to be used in:

我们将创建两个帮助函数,这些函数将用于将Book的author ObjectId 扩展为 Author 对象,并将 Author 的book ObjectId 数组扩展为 Book 对象的数组。 我在 ./schema/index.js 文件中定义了这些函数,就在解析器将要在以下函数中使用它们之前:

const books = async bookIds => {
try {
const books = await Book.find({_id: { $in: bookIds }})
return books.map(book => ({
...book._doc,
author: author.bind(this, book._doc.author)
}))
} catch {
throw err
}
}const author = async authorId => {
try {
const author = await Author.findById(authorId)
return {
...author._doc,
books: books.bind(this, author._doc.books)
}
} catch (err) {
throw err
}
}

As you can see, the books function accepts an array of Book ObjectId s as an argument and then finds all the Book documents with those IDs. It then returns the Book s mapping over them and each Book returns all of its properties — except for the Author property. We overwrite the Author property when it’s requested by calling the Author function that’s been bound to the Book ’s author’s ObjectId .

如您所见,books函数接受 Book ObjectId 数组作为参数,然后查找具有这些ID的所有 Book 文档。 然后,它返回 Book 在它们之上的映射,每 Book 返回其所有属性-除了 Author 属性。 当请求时,我们通过调用绑定到 Book 作者的 ObjectId Author 函数来覆盖 Author 属性。

The author function accepting an Author ObjectId finds the Author document associated with that ObjectId and returns all of its properties, except for the books array. The books array is overwritten when that property is requested by calling the Books function, which returns all the properties for each book that has an associated ObjectId in the Author ’s books array.

接受 Author ObjectId 的author函数查找与该 ObjectId 关联的 Author 文档,并返回其所有属性(books数组除外)。 通过调用 Books 函数来请求该属性时,books数组将被覆盖,该函数将为 Author 的books数组中具有关联的 ObjectId 每本书返回所有属性。

We bind these functions for later use. If we were to call them immediately it would result in an infinite loop with the Books function calling the Author function, which would then call the Books function, and so on.

我们绑定这些功能以供以后使用。 如果我们立即调用它们,将导致 Books 函数调用 Author 函数的无限循环,然后再调用 Books 函数,依此类推。

We can now rewrite our Query resolvers using these handy functions to inflate a Book ’s related Author and an Author ’s related Book s:

现在,我们可以使用以下方便的函数来重写 Query 解析器,以使 Book 的相关 Author Author 的相关 Book 膨胀:

const resolvers = {
Query: {
authors: async () => {
try {
const authors = await Author.find()
return authors.map(author => ({
...author._doc,
books: books.bind(this, author._doc.books)
}))
} catch (err) {
throw err
}
},
books: async () => {
try {
const books = await Book.find()
return books.map(book => ({
...book._doc,
author: author.bind(this, book._doc.author)
}))
} catch (err) {
throw err
}
}
},

There’s a lot of similarity in these queries to each other and the helper functions. The main difference from the helper functions is that we’re listing all books and all authors and using the helper functions to inflate their related properties on demand.

这些查询彼此之间以及辅助函数之间有很多相似之处。 与帮助程序功能的主要区别在于,我们列出了所有书籍和所有作者,并根据需要使用帮助程序功能来增加其相关属性。

We’ll finish off the resolver changes by rewriting the Mutation resolvers:

我们将通过重写 Mutation 解析器来完成解析器更改:

Const resolvers = {
Query: {
...
},
Mutation: {
createAuthor: async (_, { name }) => {
try {
const author = new Author({ name })
await author.save()
return author;
} catch (err) {
throw err
}
},
createBook: async (_, { name, pages, author: authorId }) => {
const book = new Book({ name, pages, author: authorId })
try {
const savedBook = await book.save()
const authorRecord = await Author.findById(authorId)
authorRecord.books.push(book)
await authorRecord.save()
return {
...savedBook._doc,
author: author.bind(this, authorId)
}
} catch (err) {
throw err
}
}
}
}

The createAuthor mutation remains unchanged except for adding the try/catch block.

除了添加try / catch块外, createAuthor 突变保持不变。

In the createBook mutation we need to rename the author parameter that we supply as an argument from author to authorId as we are going to be using the author function and the names conflict. The most important change we’re making here is after the new Book has been saved we update the related Author by finding its document, pushing the new Book s ObjectId into the Author ’s books array, and then saving the record. Just for good measure, we use our helper function to inflate the Author property on demand when the new Book is returned.

createBook 突变时,我们需要重命名我们提供的author参数作为 author authorId 自变量,因为我们将使用 author 函数,并且名称发生冲突。 我们在这里所做的最重要的更改是在保存 Book 之后,我们通过查找相关文档,将 Book ObjectId 推送到 Author 的books数组中来更新相关 Author ,然后保存记录。 出于很好的考虑,我们使用帮助函数在返回新 Book 时按需增加 Author 属性。

测试最终版本 ( Test the Final Version )

We’ve made quite a few changes since the last testing so please delete any previously created Author s and Book s in your MongoDB and then create a new Author using the createAuthor mutation. Don’t forget to save the author’s ID. We’ll need that when creating Book s.

自上次测试以来,我们已经进行了很多更改,因此请删除MongoDB中以前创建的 Author Book ,然后使用 createAuthor 突变创建一个新的 Author 。 不要忘记保存作者的ID。 创建 Book 时,我们将需要它。

Once that’s done we can see the changes by using the mutation createBook and asking for Author properties to be returned:

完成后,我们可以使用变体 createBook 并要求返回 Author 属性来查看更改:

Let’s create another Book using the same author but this time when returning the author for the new Book also return the names of all the books by that author:

让我们使用同一位作者创建另一 Book ,但是这次返回 Book 的作者时,还会返回该作者的所有书名:

Just to prove it all works, let’s use the Author s query to list all Author s and the names of their books:

为了证明这一切正常,让我们使用 Author 的查询列出所有 Author 及其书名:

However, I don’t recommend this as, apart from it being ridiculously pointless, it’s also rather slow.

但是,我不建议这样做,因为它除了荒谬无意义之外,而且速度也很慢。

As a final test to prove that the magic expanding the related properties is being done in the code and not in the background you can look in MongoDB. You should have an Author document with a Books array containing ObjectId s and some Book documents with Author properties that are also ObjectId s.

作为最终测试,证明扩展相关属性的魔力是在代码中完成的,而不是在后台完成的,您可以在MongoDB中查看。 您应该拥有一个带有一个包含 ObjectId Books 数组的 Author 文档,以及一些带有 Author 属性也是 ObjectId Book 文档。

The complete working code for this article can be found here .

可在此处找到 本文的完整工作代码。

翻译自: https://medium.com/better-programming/how-to-add-relations-to-your-graphql-schema-295dd45cc62d

graphql

一、 graphql 基本语法: https:// graphql .cn/learn/queries/#variables 二、 graphql - java graphql java 客户端): demo:https://blog.csdn.net/tangyaya8/article/details/105328461 扩展:数据关联 author对象下存在fans(一对多关联) 创建fans对象 @Entity @Data public class Fans { Restful is Great! But GraphQL is Better. – My Humble Opinion. GraphQL will do to REST what JSON did to XML. – Samer Buna from Quora GraphQL 作为 Facebook 的前端三架马车之一(另外两架是 Relay 和 React, 三者可以无缝结合),提出也有一段时间了,但真正用过的人却出奇的少, 截止到 2018 年底,根据 Stateofjs https://edu.csdn.net/course/detail/36074 Python 实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 使用Hot Chocolate和.NET 6构建 GraphQL 应用文章索引 在讨论完 GraphQL 中的查询需求后,这篇文章我们将演示如何实现 GraphQL 中的数据 添加 任务。 在 GraphQL 中,对数据进行查询使用query,而对于修改数据则需要使用muta highlight: a11y-dark GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余。 1、安装依赖 npm install gq-loader --save-dev 2、React调用接口 封装api.js export const client = new ApolloClient({ uri: postUrl, GraphQL 并不会实现关联查询,数据关联需要程序自己实现 官网首页有介绍获取多个资源只需要一个请求,如想获取用户信息和身份证信息,原来需要先查用户信息,再通过用户id查询身份证信息,而在 GraphQL 中一次请求就可以实现。 对于这个观点我不敢苟同,可能我还没有体会到这种感觉,我认为只要需求明确,多个资源一次请求在RESTFUl中同样可以实现。 废话不说了,进入在正题 本文将从 GraphQL 是什么,为什么要使用 GraphQL ,使用 GraphQL 创建简单例子,以及 GraphQL 实战,四个方面对 GraphQL 进行阐述。说得不对的地方,希望大家指出斧正。 github项目地址:https://github.com/Charming2015/ graphql -todolist 一、 GraphQL 是什么? 关于 GraphQL 是什么,网上一搜一大堆。根据官网的解释就是一... GraphQL 服务端的库和应用可以用各种语言实现,所以这里的示例脱离语言。 GraphQL 服务端的应用代码的基本实现流程(需要提前安装好 GraphQL 库,各种语言的库参考这里): 定义用户自定义类型。类型的每个字段都必须是已定义的,且最终都是 GraphQL 中定义的类型。 定义根类型。每种根类型中包含了准备暴露给服务调用方的用 GraphQL 应用程序中的五个常见问题(以及如何修复它们) 学习解锁 GraphQL 的强大功能而不会遇到其缺点 GraphQL 现在风靡一时,并且有充分的理由:它是一种优雅的方法,可以解决许多与传统REST API相关的问题。 但是,如果我告诉你 GraphQL 没有自己的问题,那我就撒谎了。如果你不小心,这些问题可能不仅会导致代码库臃肿,甚至会导致应用程序显着减慢。 我在谈论的问题包括: 1.架构...