GraphQL circular dependency

2019-05-09 20:44发布

问题:

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

回答1:

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.



回答2:

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!