TheLearner
TheLearner

Reputation: 2863

Modularizing GraphQL Types into separate files

I have a GraphQL implementation with a single monolithic types/index.js file that currently contains two type definitions:

const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');

const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull,
} = graphql;

const BookType = new GraphQLObjectType({
  name: 'Book',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    genre: { type: GraphQLString },
    author: {
      type: AuthorType,
      resolve: (parent, args) => {
        // code to get data from db
        return Author.findById(parent.authorId);
      },
    },
  }),
});

const AuthorType = new GraphQLObjectType({
  name: 'Author',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    age: { type: GraphQLInt },
    books: {
      type: new GraphQLList(BookType),
      resolve: (parent, args) => {
        // code to get data from db
        return Book.find({authorId: parent.id});
      },
    },
  }),
});

module.exports = {BookType, AuthorType};

This is the file I import into my schema.js file where it's used by root queries and mutations:

const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull,
} = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');
const {BookType, AuthorType} = require('../types');

// QUERIES
//------------------------------------------------------------------------------------------------------
const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    book: {
      type: BookType,
      args: { id: { type: GraphQLID } },
      resolve: (parent, args) => {
        // code to get data from db
        return Book.findById(args.id);
      },
    },
    author: {
      type: AuthorType,
      args: { id: { type: GraphQLID } },
      resolve: (parent, args) => {
        // code to get data from db
        return Author.findById(args.id);
      },
    },
    books: {
      type: new GraphQLList(BookType),
      resolve: (parent, args) => {
        // code to get data from db
        return Book.find({});
      },
    },
    authors: {
      type: new GraphQLList(AuthorType),
      resolve: (parent, args) => {
        // code to get data from db
        return Author.find({});
      }
    },
  },
});

// MUTATIONS
//------------------------------------------------------------------------------------------------------
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addAuthor: {
      type: AuthorType,
      args: {
        name: { type: new GraphQLNonNull(GraphQLString) },
        age: { type: new GraphQLNonNull(GraphQLInt) }
      },
      resolve(parent, args) {
        let author = new Author({
          name: args.name,
          age: args.age
        });
        return author.save();
      }
    },
    addBook: {
      type: BookType,
      args: {
        name: { type: new GraphQLNonNull(GraphQLString) },
        genre: { type: new GraphQLNonNull(GraphQLString) },
        authorId: { type: new GraphQLNonNull(GraphQLID) },
      },
      resolve(parent, args) {
        let book = new Book({
          name: args.name,
          genre: args.genre,
          authorId: args.authorId,
        });
        return book.save();
      },
    },
  }
});

module.exports = new GraphQLSchema({
  query: RootQuery,
  mutation: Mutation,
});

But as the project grows, I am anticipating dozens of types with tons of two-way relationships. So I'd like to modularize all my types into individual files, such as types/BookType.js, types/AuthorType.js, etc. rather than a single types/index.js as I have right now. What's the best way to accomplish this given the two-way relationships?

Upvotes: 2

Views: 545

Answers (1)

Saransh Barua
Saransh Barua

Reputation: 21

While segregating the types into separate files, you'll need to handle two-way relationships. In this case, AuthorType needs BookType and vice-versa. So you'll need to import AuthorType in types/BookTypes.js and BookType in types/AuthorType.js but this will introduce a classic circular dependency issue (before AuthorType exports it demands BookType and vice-versa) which is common in node projects. You can read more about it here. To handle this, shift your require calls at the end of the file in both types. So your code looks somewhat like this:

types/BookType.js

const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');

const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull,
} = graphql;

const BookType = new GraphQLObjectType({
  name: 'Book',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    genre: { type: GraphQLString },
    author: {
      type: AuthorType,
      resolve: (parent, args) => {
        // code to get data from db
        return Author.findById(parent.authorId);
      },
    },
  }),
});

module.exports = BookType;

// This is here to prevent circular dependencies problem which will lead to the formation of infinite loop
const AuthorType = require("./AuthorType");

types/AuthorType.js

const graphql = require('graphql');
const Book = require('../../../models/book');
const Author = require('../../../models/author');

const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull,
} = graphql;

const AuthorType = new GraphQLObjectType({
  name: 'Author',
  fields: () => ({
    id: {
      type: GraphQLID
    },
    name: {
      type: GraphQLString
    },
    age: {
      type: GraphQLInt
    },
    books: {
      type: new GraphQLList(BookType),
      resolve: (parent, args) => {
        // code to get data from db
        return Book.find({
          authorId: parent.id
        });
      },
    },
  }),
});

module.exports = AuthorType;

// This is here to prevent circular dependencies problem which will lead to the formation of infinite loop
const BookType = require("./BookType");

Also, it is better to have a types/index.js which will act as a handler for import/exports. You export every type to index.js and take whatever you want from it anywhere. This saves you from a lot of messy code because now you can do something like this:

const { BookType, AuthorType, OtherType } = require("../types/index");

Upvotes: 2

Related Questions