5

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 })
        }
      }
    }
  })
})
user3142695
  • 15,844
  • 47
  • 176
  • 332
  • Your query and schema definition matches which is what you get as output.. Nest the content type in the article type to get the nested structure, You can have separate resolver to pull the article and content from its own collection based on the args – s7vr Oct 29 '17 at 21:55
  • what do you mean by "separate resolver"? Could you post some code please? – user3142695 Oct 29 '17 at 22:30
  • I can but I'm not really clear why you have the query the way you have. Why not use query like `String query = { article(id: "Dn59y87PGhkJXpaiZ") { _id, content(language:"en") { content, timestamp } } }` and update your schema to embed the content in the articles as shown [here](https://stackoverflow.com/a/47005404/2683814). What am I missing here ? I have also added a complete working java example there. Please take a look and try to explain how is this different from that post. – s7vr Oct 29 '17 at 22:32
  • Your linked code is nearly the same as I'm using. With that I do get the array which would be in this example `articleContent` and it's data comes from `find({ main: args.id })`. Additionally I need the title of the dataset. This data comes from `find({ _id: args.id})`, which is another document. And this is what makes the trouble for me. – user3142695 Oct 29 '17 at 23:21
  • I tried to nest the content into article, but get an undefined db. See updated post. – user3142695 Oct 29 '17 at 23:27
  • Are you looking for this kind of structure ? `{ "data": { "article": [ { "_id": "9uPjYoYu58WM5Tbtf", "title": "parent", "content": [ { "content": "Third paragraph", "timestamp": 1484939404 } ] }, { "_id": "345869696665", "title": "parent", "content": [ { "content": "First paragraph", "timestamp": 1484939404 } ] } ] } }` – s7vr Oct 30 '17 at 00:22

1 Answers1

2

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.

Bear-Foot
  • 744
  • 4
  • 12