GraphQL circular dependency
Asked Answered
I

3

11

I am fairly new to javascript and am currently learning to implement a graphQL API with a MongoDB backend using Node.js. I am running into a problem with a circular dependency between two types.

Basically, I have a classic blog post/blog author situation. A post only has one author and therefore the mongoose schema holds a reference to that author.

In my graphQL type "Author" I want to add a field "posts" which allows me to navigate from authors to all the posts they have written. The reference is not coded in the database models but retrieved through the controllers. Here's my blog post code.

var graphql = require("graphql");
var AuthorResolvers = require("../resolvers/author");
var PostResolvers = require("../resolvers/post");
var AuthorType = require("./author").AuthorType;

var PostType = new graphql.GraphQLObjectType({
  name: "PostType",
  fields: {
        _id: {
            type: graphql.GraphQLID
        },
        title: {
            type: graphql.GraphQLString
        },
        author: {
            type: AuthorType,
            description: "Author of this post",
            resolve: (post) => AuthorResolvers.retwArgs(post)
        }
    }
});

module.exports = {PostType};

The resolvers.js file only exports functions to address the controllers.

My authors type is defined as follows:

var graphql = require ("graphql");
var AuthorResolvers = require("../resolvers/author");
var PostResolvers = require("../resolvers/post");

var AuthorType = new graphql.GraphQLObjectType({
  name: "AuthorType",
  fields: () => {
        var PostType = require("./post");
        return {
            _id: {
                type: graphql.GraphQLID
            },
            name: {
                type: graphql.GraphQLString
            },
            posts: {
                type: new graphql.GraphQLList(PostType),
                description: "Posts written by this author",
                resolve: (author) => PostResolvers.retwArgs(author)
            }
        }
    }
});

There're two things in here which I already tried:

  1. I used a function to return the fields field. I think this is called a thunk or a closure.
  2. I require the PostType in the function returning the fields. When i required the file ./post together with the other requires, the error was already thrown at the top of the file.

when i try to run this server example, i recieve the error:

Can only create List of a GraphQLType but got: [object Object].

which points to the line

type: new graphql.GraphQLList(PostType)

in authors.

The files containing the type definitions posted above also export queries like this:

var PostQuery = {
    posts: {
        type: new graphql.GraphQLList(PostType),
        description: "Posts of this Blog",
        resolve: PostResolvers.ret
    }
};

for the post and like this

var AuthorQuery = {
    authors: {
        type: new graphql.GraphQLList(AuthorType),
        description: "The authors working on this Blog",
        resolve: AuthorResolvers.ret
    }
};

for the author respectively. Everything is brought together in a Schema file like this:

var graphql = require("graphql");
var Author = require("./types/author").AuthorQuery;
var Post = require("./types/post").PostQuery;

var root_query = new graphql.GraphQLObjectType({  
  name: "root_query",
  fields: {
    posts: Post.posts,
    authors: Author.authors
  }
});

module.exports = new graphql.GraphQLSchema({
  query: root_query
});

and finally the server:

var graphql = require ('graphql').graphql  
var express = require('express')  
var graphQLHTTP = require('express-graphql')
var Schema = require('./api/schema')

var app = express()
  .use("/", graphQLHTTP(
    {
      schema: Schema,
      pretty: true,
      graphiql: true,
    }
  ))
  .listen(4000, function(err) {
    console.log('Running a GraphQL API server at localhost:4000');
  })

I really don't see any way to resolve this circular dependency. If i simply comment out the references to the PostType in the AuthorType definitions, The server starts without problems. Any help here would be greatly appreciated.

Maybe for better understanding. The directory structure looks like this:

│   package.json
│   server.js
│
├───api
│   │   schema.js
│   │
│   ├───controllers
│   │       author.js
│   │       post.js
│   │
│   ├───models
│   │       author.js
│   │       post.js
│   │
│   ├───resolvers
│   │       author.js
│   │       post.js
│   │
│   └───types
│           author.js
│           post.js
│
└───config
        db.js
Indisposed answered 1/3, 2017 at 12:8 Comment(3)
What happens if you try defining fields like fields: () => ({ _id: ..., author: ... })?Kreager
Then I'll have to put the var PostType = require("./post"); at the top of the file and instead get the error in a different class. The output is then PostType.author field type must be Output Type but got: undefined. and strangely points to the generation of the GraphQL schema.Indisposed
If i then add another function to also retrieve the fields in PostType with a function as you defined, the same error is thrown.Indisposed
K
6

This is rather typical problem of modules circular dependency - you can refer to this question How to deal with cyclic dependencies in Node.js. In this case it has nothing to do with GraphQL.

What is more, you do module.exports = { PostType } however in AuthorType you perform var PostType = require('./post'), shouldn't that be var PostType = require('./post').PostType? I suppose this is the cause of below error you get:

Can only create List of a GraphQLType but got: [object Object].

Because your PostType is now { PostType: ... } and not a GraphQLObjectType instance.

Kreager answered 1/3, 2017 at 12:41 Comment(1)
Thank you very much. The PostType was indeed a type which i fixed. It wasn't the problem, though. Your link led me to the correct answer which is: make sure your exports are defined before any require statements import anything. (I'm paraphrasing) Thanks a lot.Indisposed
I
8

piotrbienias' response led me to the correct thread. I've read it before but didn't understand completely. It is necessary to define the exports before you require the class. In my case, i was able to fix it like this:

module.exports.AuthorType = new graphql.GraphQLObjectType({
  name: "AuthorType",
  fields: () => {
        var PostType = require("./post").PostType;
        return {
            _id: {
                type: graphql.GraphQLID
            },
            name: {
                type: graphql.GraphQLString
            },
            posts: {
                type: new graphql.GraphQLList(PostType),
                description: "Posts written by this author",
                resolve: (author) => PostResolvers.retwArgs(author)
            }
        }
    }
});

and similarly for the PostType. This way, the export is defined before the require is called.

Thanks so much!

Indisposed answered 1/3, 2017 at 13:13 Comment(2)
This is the solution for me -- the key is to use a callback function for the fields parameter and put your require() in that functionAlternant
This is a pattern known as a "thunk".Ebner
K
6

This is rather typical problem of modules circular dependency - you can refer to this question How to deal with cyclic dependencies in Node.js. In this case it has nothing to do with GraphQL.

What is more, you do module.exports = { PostType } however in AuthorType you perform var PostType = require('./post'), shouldn't that be var PostType = require('./post').PostType? I suppose this is the cause of below error you get:

Can only create List of a GraphQLType but got: [object Object].

Because your PostType is now { PostType: ... } and not a GraphQLObjectType instance.

Kreager answered 1/3, 2017 at 12:41 Comment(1)
Thank you very much. The PostType was indeed a type which i fixed. It wasn't the problem, though. Your link led me to the correct answer which is: make sure your exports are defined before any require statements import anything. (I'm paraphrasing) Thanks a lot.Indisposed
A
3

According to this article graphQL does somewhat increase issues with circular dependency. The article suggests using extend type for defining interdependent types. It worked in my case.

So instead of defining the types the 'staightforward' way:

type User {
    id: ID
    posts: [Post]
}
type Post {
    id: ID
    user: User
}

you should use a one-directional definition adding the other dependency direction afterwards using extend like this:

type User {
    id: ID 
    // no reference to [Post] !
}
type Post {
    id: ID
    user: User // this stays - single direction
}
extend type User {
    posts: [Post]
}

See the article for justification and more detailed example.

Annadiane answered 20/9, 2021 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.