How to use Mongoose schema to generate graphQL types
In this article, I will tell you how, based on my experience, I wrote a small npm module that helped me and, I think, can help you save a decent amount of time and reduce the code by almost half.
It all started with the fact that I decided to write an isomorphic CMS for one of my projects using the following technologies:
A simplified architecture is as follows:
Accordingly, for each entity in the database, we must create a Mongoose scheme in order to be able to manipulate the data. It looks like this:
Now, if we use graphQL, we must create a type for each entity in the database, and the type of each property must be identical to the same as in the Mongoose scheme. Without graphQL types, we simply cannot generate a graphQL schema. The type is as follows:
I think the similarity is obvious? Now imagine that you have several dozen entities and for each of them you need to practically duplicate the same logic. But types can contain arrays of other types and many other nested elements.
Actually, to solve this problem, I wrote a small module (only 3kb) that will help avoid duplication of logically identical code and almost halve your code, in my case I cut the code by more than 2000 lines.
First, install the module:
The module contains one single function “MTGQL”, which takes one object with configurations as an argument. The object has the following structure:
A couple of examples of using the module below:
dbSchema.js
In the file with your graphQL types:
type.js
Actually, only 10 lines of code, which are equivalent to the following:
I think the difference is obvious. The module does not directly support “GraphQLFloat”, because in the Mongoose scheme you can only specify the type “Number”, which is equivalent to an integer. But this problem can be easily solved by passing the desired property through the configuration object.
You can take a closer look at how the module works on GitHub .
I hope the article was useful to you, if you have any suggestions for improvement, write, I will definitely consider it. Thanks for attention.
The original article is on medium.com .
It all started with the fact that I decided to write an isomorphic CMS for one of my projects using the following technologies:
- React - for building a UI
- Express - as a server
- MongoDb + Mongoose - noSQL database
- graphQL - the main API for interacting with the database
- Apollo-Client - a connector for conveniently invoking queries and mutations through graphQL
- webpack - for building a project and separating client and server code
A simplified architecture is as follows:
__root
1 |__client
2 |__public
3 |__middleware
4 |__server
- React Components
- Client code bundle and other public files
- Mongoose: schemas, additional methods, and graphQL: types, class with queries, class with mutations, schema
- Server code bundle with express server
Accordingly, for each entity in the database, we must create a Mongoose scheme in order to be able to manipulate the data. It looks like this:
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, if we use graphQL, we must create a type for each entity in the database, and the type of each property must be identical to the same as in the Mongoose scheme. Without graphQL types, we simply cannot generate a graphQL schema. The type is as follows:
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}
}
});
I think the similarity is obvious? Now imagine that you have several dozen entities and for each of them you need to practically duplicate the same logic. But types can contain arrays of other types and many other nested elements.
Actually, to solve this problem, I wrote a small module (only 3kb) that will help avoid duplication of logically identical code and almost halve your code, in my case I cut the code by more than 2000 lines.
First, install the module:
npm i mongoose-schema-to-graphql --saveThe module contains one single function “MTGQL”, which takes one object with configurations as an argument. The object has the following structure:
let configs = {
name: 'couponType',
// название graphQL типа
description: 'Coupon base schema',
// описание graphQL типа
class: 'GraphQLObjectType',
// graphQL класс который будет использован для сборки
schema: couponSchema,
// Mongoose схема
exclude: ['_id'],
// свойства которые нужно исключить
props: {
price: {type: GraphQLFloat}
}
// дополнительные свойства которые нужно добавить или переписать
}
A couple of examples of using the module 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 the file with your graphQL types:
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);
Actually, only 10 lines of code, which are equivalent to the following:
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 obvious. The module does not directly support “GraphQLFloat”, because in the Mongoose scheme you can only specify the type “Number”, which is equivalent to an integer. But this problem can be easily solved by passing the desired property through the configuration object.
You can take a closer look at how the module works on GitHub .
I hope the article was useful to you, if you have any suggestions for improvement, write, I will definitely consider it. Thanks for attention.
The original article is on medium.com .