GraphQL with MongoDB using Mongoose and Node.js

As part of a prototype/exploratory project in March 2016, I explored how to build a GraphQL server using Node.js and interfacing with a MongoDB database using the Mongoose library.

The best reason to use GraphQL is if you have multiple frontend clients that communicate with your backend and they each have differing data needs.

For instance, if you have a mobile application and a website that communicate with the same backend API, they will need different data. The mobile app may want to make as few requests as possible on startup and batching the requests into one request is possible with GraphQL.

GraphQL Schema Example

import {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLSchema,
  GraphQLString,
  GraphQLInt,
  GraphQLList,
  GraphQLID,
  GraphQLBoolean
} from 'graphql/type';
import co from 'co';
import mongoose from 'mongoose';

import models from './models';

var makeGQLString = function(desc) {
  return {
    type: GraphQLString,
    description: desc
  };
};

var makeNonNullGQLString = function(desc) {
  return {
    type: new GraphQLNonNull(GraphQLString),
    description: desc
  };
};

var makeGQLBoolean = function(desc) {
  return {
    type: GraphQLBoolean,
    description: desc
  };
};

var makeGQLInt = function(desc) {
  return {
    type: GraphQLInt,
    description: desc
  };
};

var listingType = new GraphQLObjectType({
  name: 'Listing',
  description: 'An event listing',
  fields: function() {
    return {
      id: makeNonNullGQLString('The id of the listing.'),
      slug: makeNonNullGQLString('slug'),
      title: makeGQLString('The title of the listing.'),
      description: makeGQLString('description'),
      description_html: makeGQLString('description_html'),
      category_id: GraphQLID,
      category_key: makeGQLString('category_key'),
      hashtag: makeGQLString('hashtag'),
      location: makeGQLString('location'),
      website: makeGQLString('website'),
      show_count: makeGQLInt('show count'),
      show_avatars_of_bookers: makeGQLBoolean('show_avatars_of_bookers'),
      show_tickets_sold_count: makeGQLBoolean('show_tickets_sold_count'),
      hide_date: makeGQLBoolean('hide_date'),
      capacity: makeGQLString('Capacity of the listing'),
      state: makeNonNullGQLString('State of the listing')
    };
  }
});

var eventType = new GraphQLObjectType({
  name: 'Event',
  description: 'An event',
  fields: function() {
    return {
      id: makeNonNullGQLString('Event id'),
      start_stamp: makeNonNullGQLString('Start of the event, timestamp'),
      end_stamp: makeNonNullGQLString('End of the event, timestamp'),
      city_name: makeGQLString('Event that the city is in')
    };
  }
});

var rootQueryType =  new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    listings: {
      type: new GraphQLList(listingType),
      resolve: function(parent, args, ast) {
        return models.Listing.find().limit(10);
      }
    },
    listing: {
      type: listingType,
      args: {
        id: {
          name: 'id',
          type: new GraphQLNonNull(GraphQLString)
        }
      },
      resolve: function(parent, args, ast) {
        return models.Listing.findOne({ _id: args.id });
      }
    },
    events: {
      type: new GraphQLList(eventType),
      args: {
        listing_id: {
          name: 'listing_id',
          type: new GraphQLNonNull(GraphQLString)
        }
      },
      resolve: function(parent, args, ast) {
        return models.Event.find({ listing_id: args.listing_id });
      }
    }
  }
});

var schema = new GraphQLSchema({
  query: rootQueryType
});

export var getProjection;
export default schema;

We declare a root query that contains certain fields that are resolved by particular functions. The type of each field can be a list, non-null or other types. The resolve function is what fetches data from the database (or whatever caching layer(s) you have).

Within the GraphQLObjectType we can define a structured object with more fields and define the types of those fields. This is in contrast to most REST APIs which provide no schema.

It’s also possible, as you define the fields, to provide descriptions of them. These descriptions can appear in a GraphQL API explorer and since they are built-in and supported as part of the GraphQL specification, the descriptions can be supported by multiple libraries. In contrast, most REST APIs do not place descriptions near the fields within the code; they provide the descriptions (if at all) within separate documentation.

When building this prototype to provide a GraphQL interface to a MongoDB database, it was very easy to declare the schemas for the models and then to provide them as part of the interface.