How to use Mongoose schema to generate graphQL type.

First time I started to work with graphQL I thought, “wow that’s awesome! I can build scalable web app. with absolutely new view to architecture”.

So I started to build new CMS for one of my project on top of React, graphQL, Apollo and MongoDb.

Simplified app. architecture looks like the following:

__root
1 |__client
2 |__public
3 |__middleware
4 |__server
  1. Contains all React components. Each component has presentational and connecter “Apollo-client” to pass fetched data and mutation methods to component.
  2. Client-side application bundle and other public data.
  3. Mongoose: schemas, methods, helpers and graphQL: types, queries, mutations and schema.
  4. Server-side code bundle with express server.

I also use webpack to build app and separate client and server side code.

As soon as the project was coming together, I had seen that for each essence and collection in data-base I have to almost duplicate the same schema which I already created for Mongoose to create graphQL type.

For example we have collection “coupons”:

let couponSchema = mongoose.Schema({
couponCode: Array,
description: String,
discountAmount: String,
minimumAmount: String,
singleUseOnly: Boolean,
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
expirationDate: mongoose.Schema.Types.Date
});

Now we have to create graphQL type to be able to fetch or manipulate data from our data-base:

let couponType = new GraphQLObjectType({
name: 'couponType',
description: 'single use coupon',
fields: {
_id: {type: GraphQLString},
couponCode: {type: new GraphQLList(GraphQLString)},
description: {type: GraphQLString},
discountAmount: {type: GraphQLString},
minimumAmount: {type: GraphQLString},
singleUseOnly: {type: GraphQLBoolean},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
expirationDate: {type: GraphQLString}
}
});

So as you can see it’s pretty much the same logic as in Mongoose schema. Now imagine that you have a couple tens of essences with different structure, nested elements and etc. You’ll have to retype almost same thing for each of them which is a lot of logically duplicated codes!


To avoid this problem I built a small npm package: “mongoose-schema-to-graphql”. It’s just 3kb of code which will save you a lot of time and prevent typing useless lines of code.

Basically this package uses your existed Mongoose schema, (which you anyway have to declare to create collection) and based on that it returns a generated graphQL class with the same structure.

All we need is:

npm i mongoose-schema-to-graphql --save

After that you have to import “MTGQL” function from the package which takes one config object as argument with the following structure:

let configs = {
name: 'couponType',
//graphQL type's name
              description: 'Coupon base schema', 
//graphQL type's description
              class: 'GraphQLObjectType', 
//"definitions" class name
              schema: couponSchema, 
//your Mongoose schema
              exclude: ['_id'], 
//fields which you want to exclude from mongoose schema
              props: {
price: {type: GraphQLFloat}
}
//add custom properties or overwrite existed
}

Couple examples on how it works below:

dbSchema.js
import mongoose from 'mongoose';
let selectObj = {
value: String,
label: String
};

let answerSchema = mongoose.Schema({
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
title: String,
answersImage: String,
recommended: [selectObj],
isPublished: Boolean
});

export let questionSchema = mongoose.Schema({
question: String,
defRecommended: [selectObj],
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
isPublished: Boolean,
multipleChoice: Boolean,
answers: [answerSchema]
});

In your graphQLs type code:

type.js
import MTGQL from 'mongoose-schema-to-graphql';
import {questionSchema} from './dbSchemas';

let config = {
name: 'questionType',
description: 'Question collection\'s type',
class: 'GraphQLObjectType',
schema: questionSchema,
exclude: ['_id']
};

export let questionType = MTGQL(config);

Just 10 lines of code which equal to:

import {
GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLList,
GraphQLInt
} from 'graphql';

let selectType = new GraphQLObjectType({
name: 'selectType',
fields: {
value: {type: GraphQLString},
label: {type: GraphQLString}
}
});

let answerType = new GraphQLObjectType({
name: 'answerType',
description: 'answer type for question',
fields: {
title: {type: GraphQLString},
answersImage: {type: GraphQLString},
recommended: {type: new GraphQLList(selectType)},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
isPublished: {type: GraphQLBoolean}
}
});

export let questionType = new GraphQLObjectType({
name: 'questionType',
description: 'Question collection\'s type',
fields: {
question: {type: GraphQLString},
defRecommended: {type: new GraphQLList(selectType)},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
isPublished: {type: GraphQLBoolean},
multipleChoice: {type: GraphQLBoolean},
answers: {type: new GraphQLList(answerType)}
}
});

I think the difference is evident. Package doesn’t support “GraphQLFloat” out of the box because in Mongoose schema you can declare only integer numbers, but you can easily handle it by just passing property manually in config object.

More information on how it works can be found on GitHub.

I hope this package and article will be useful for you.

If you have any suggestions just let me know.

Thank You.