Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to nest two graphQL queries in a schema?

I've created a GraphQLSchema with two fields, both using a resolve() to get the data from a mongoDB.

With that, the query...

{
  article(id: "Dn59y87PGhkJXpaiZ") {
    title
  },
  articleContent(id: "Dn59y87PGhkJXpaiZ") {
    _id,
    content(language: "en"),
    type
  }
}

...results in:

{
  "data": {
    "article": {
      "title": "Sample Article"
    },
    "articleContent": [
      {
        "_id": "Kho2N8yip3uWj7Cib",
        "content": "group",
        "type": "group"
      },
      {
        "_id": "mFopAj4jQQuGAJoAH",
        "content": "paragraph",
        "type": null
      }
    ]
  }
}

But I need a result structure like this (content should be inside of article object):

Expected result

{
  "data": {
    "article": {
      "title": "Sample Article",
      "content": [
        {
          "_id": "Kho2N8yip3uWj7Cib",
          "content": "group",
          "type": "group"
        },
        {
          "_id": "mFopAj4jQQuGAJoAH",
          "content": "paragraph",
          "type": null
        }
      ]
    },
  }
}

For me the problem are both async mongoDB resolves in my schema:

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {

      article: {
        type: new GraphQLObjectType({
          name: 'article',
          fields: {
            title: {
              type: GraphQLString,
              resolve (parent) {
                return parent.title
              }
            }
          }
        }),
        args: {
          id: { type: new GraphQLNonNull(GraphQLID) }
        },
        async resolve ({ db }, { id }) {
          return db.collection('content').findOne({ _id: id })
        }
      },

      articleContent: {
        type: new GraphQLList(new GraphQLObjectType({
          name: 'articleContent',
          fields: {
            _id: { type: GraphQLID },
            type: { type: GraphQLString },
            content: {
              type: GraphQLString,
              args: {
                language: { type: new GraphQLNonNull(GraphQLString) }
              },
              resolve (parent, { language }, context) {
                return parent.content[language][0].content
              }
            }
          }
        })),
        args: {
          id: { type: new GraphQLNonNull(GraphQLID) }
        },
        async resolve ({ db }, { id }) {
          return db.collection('content').find({ main: id }).toArray()
        }
      }
    }
  })
})

Update

If I nest the content inside the article, I do get the error Cannot read property 'collection' of undefined

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {

      article: {
        type: new GraphQLObjectType({
          name: 'article',
          fields: {
            title: {
              type: GraphQLString,
              resolve (parent) {
                return parent.title
              }
            },
            articleContent: {
              type: new GraphQLList(new GraphQLObjectType({
                name: 'articleContent',
                fields: {
                  _id: { type: GraphQLID },
                  type: { type: GraphQLString },
                  content: {
                    type: GraphQLString,
                    args: {
                      language: { type: new GraphQLNonNull(GraphQLString) }
                    },
                    resolve (parent, { language }, context) {
                      return parent.content[language][0].content
                    }
                  }
                }
              })),
              args: {
                id: { type: new GraphQLNonNull(GraphQLID) }
              },
              async resolve ({ db }, { id }) { // db is undefined here!!
                return db.collection('content').find({ main: id }).toArray()
              }
            }
          }
        }),
        args: {
          id: { type: new GraphQLNonNull(GraphQLID) }
        },
        async resolve ({ db }, { id }) {
          return db.collection('content').findOne({ _id: id })
        }
      }
    }
  })
})
like image 969
user3142695 Avatar asked Oct 30 '22 00:10

user3142695


1 Answers

First, let's analyze the signature of a resolver.

function resolve(root, args, context)

root is the value returned by the parent resolver. This is why you get Cannot read property 'collection' of undefined because the parent resolver didn't return an object with a db property.

args are the argument passed to the field, like so: article(id:'someid') when writing the query.

context is a parameter that is passed to every resolver, and is mostly used to make accessible API-wide utilities, like your db connection.

To have db set inside your context, you can initialize your GraphQL server with it.

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  context: {
    db: db
  },
  graphiql: true,
}));

About the nesting now, you could have something like this.

export default new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      article: {
        args: {
          id: { type: new GraphQLNonNull(GraphQLID) }
        },
        resolve (_, { id }) {
          return id; // will make it accessible to children resolvers
        }
        type: new GraphQLObjectType({
          name: 'article',
          fields: {
            title: {
              async resolve (id /* resolved by article */, _, { db } /* db from context */) {
                const article = await db.collection('content').findOne({ _id: id });
                return article.title;
              }
              type: GraphQLString,
            },
            content: {
              async resolve (id /* resolved by article */, _, { db } /* db from context */) {
                const contents = await db.collection('content').find({ main: id }).toArray();
                return contents;
              }
              type: new GraphQLList(new GraphQLObjectType({
                name: 'articleContent',
                fields: {
                  _id: { type: GraphQLID },
                  type: { type: GraphQLString },
                  content: {
                    args: {
                      language: { type: new GraphQLNonNull(GraphQLString) }
                    },
                    aync resolve (parent /* resolved in content */, { language }) {
                      return parent.content[language][0].content
                    }
                    type: GraphQLString,
                  }
                }
              })),
            }
          }
        }),
      }
    }
  })
})

In order, this will happen:

  • article gets its parameter id and returns it, giving it to children resolvers.

  • title and outer content will both fire their request in parallel, accessing the db in context.

  • when outer content gets back from the db, the inner content field of every element will use their parameter language to return the right result.

like image 52
Bear-Foot Avatar answered Nov 15 '22 04:11

Bear-Foot