GraphQL developed by Facebook in 2012 and publicly available in 2015. GraphQL is alternative of REST full web service. It allows clients Query with flexible data structure which are required for client. It also support the data manipulation and modification functionlaity named mutation
Like rest api. GraphQL provide official middleware for express server named express-graphql
.
Over the past two month, I tried to learn about graphql
and graphql
connection with database
using express js
. I read some blogs and article. Most of the author provide the example or tutorial which graphql
is related to static
json data. Some blog are provide simple example with one graphql's
type
.
So I am tried to read those blogs and graphql
documentation again and again. Last tow week ago, I think about some idea about how i connect the mongodb
with graphql
using express js
as web server. create the example which provides CRUD operation functionality with tow mongodb
collections
with one to many
relation. this project will provide the idea about how developer handle the one to many
and many to many
relation graphql's
type
using mongoose
and graphql-express
.This Project will also show the path how to build better web service structure using graphql with express js.
- clone the repository
git clone https://github.com/tariqulislam/express-graphql-basic-server.git
- For Yarn run command
yarn install
- Or for npm run command
npm install
EXPORESS-GRAPHQL-WITH-MONGOOSE
|--------------
|--| config
|----| database.js (database configuration file)
|--|db (mongodb exported collection)
|----| <mongodb collection>.json (mongodb exported file)
|--| src (Project soruce file directory)
|----| graphql (All graphql related file directory)
|------| mutations (All graphql mutation related file directory)
|--------| <Type Wise Mutation>.js (Graphql type wise mutation files)
|--------| index.js (export all mutation to other module by this file)
|------| queries (All GraphQL Query related files)
|--------| <GraphQL type file>.js (Individual Graphql type related file)
|--------| index.js (GraphQL Root Type file)
|----| models (All mongoose ODM related file)
|------| <mongoose model>.js (mongoose model and schema related file)
|----| schema.js (GraphQL Schema building file)
|--| .env (node js environment variable related file)
|--| index.js (main file for run web server and connection mongoose create GraphQL Endpoint)
- Install the
mongodb
andStudio 3T
non commercial version. - Create Database to mongodb named
graphqltest
- Add username and password to mongodb and provide all permission to database.
- Grant all permission to user.
- Import
json
file fromdb
folder tographqltest
database. - Change the
.env
file for database configuration - Run the command
yarn start
ornpm start
DATABASE_HOST=localhost
DATABASE_PORT=27017
DATABASE_USER=<username>
DATABASE_PASS=<password>
DATABASE_NAME=graphqltest
- GraphQL
- express JS
- dotenv
- mongoose
- Graphqli
- nodemon
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools!alt text.
GraphQL HTTP server with any HTTP web framework that supports connect styled middleware, including Connect itself, Express and Restify.
Mongoose is an object data modeling (ODM) library that provides a rigorous modeling environment for your data, enforcing structure as needed while still maintaining the flexibility that makes MongoDB powerful.
Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
- Go to
src/models
folder - Create file
Author.js
andPost.js
// src/models/Author.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const authorSchema = new Schema({
name: { type: String },
email: {type: String, required: true, unique: true}
});
const Author = mongoose.model('Author', authorSchema);
module.exports = Author;
// src/models/Post.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const postSchema = new Schema({
author_id: Schema.Types.ObjectId,
title: String,
category: String,
body: String
});
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
this code snappit contains the model
defination for mongodb
using mongoose
ODM.
- first we add
mongoose
by addingjavascript const mongoose = require('mongoose');
- then add the
javascript const Schema = mongoose.Schema;
schema for support different datatype and constraint ofmongoose
- Schema defination for
authorSchema
andpostSchema
contains themongodb
collection
information
GraphQL has its own type language that’s used the write GraphQL schemas: The Schema Definition Language (SDL). Schema contains the some attributes named.!GraphQL Documentation
- Query
- Mutation
- Subscription
The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this!GraphQL Documentation:
type PostType {
id: ID!
title: String!
body: String!
author: [AuthorType]!
}
AuthorType
: is a Graphql Object Typeid
,title
andbody
,AuthorType
is field name of Object TypeString
andID
isDataType
of thefield
. this type is called asScalarType
and- After
DataType
!
symbol meansnon-nullable
[AuthorType]!
meansarray
ofAuthorType
object which is embedded withAuthorType
Object Type
const { GraphQLString, GraphQLObjectType, GraphQLNonNull } = require('graphql');
const AuthorType = new GraphQLObjectType({
name: 'AuthorType',
description: "This represent an author",
fields: () => ({
_id: {type: new GraphQLNonNull(GraphQLString)},
name: {type: new GraphQLNonNull(GraphQLString)},
email: {type: GraphQLString}
})
});
module.exports = AuthorType;
express-graphql
provide theGraphQlObjectType
which is provide all functionality of graphqlType
andQuery
- I define
AuthorType
as aGraphQLObjectType
atsrc/graphql/queries/AuthorType.js
const { GraphQLString, GraphQLObjectType, GraphQLNonNull } = require('graphql');
const Author = require('../../models/Author');
const AuthorType = require('./AuthorType');
const mongoose = require('mongoose');
const PostType = new GraphQLObjectType({
name: 'PostType',
description: "This is resent post",
fields: () => ({
_id: {type: new GraphQLNonNull(GraphQLString)},
title: {type: GraphQLString},
body: {type: GraphQLString},
author_id: {type: GraphQLString},
author: {type: AuthorType, resolve: async function (post) {
var authors = await Author.findById(mongoose.Types.ObjectId(post.author_id))
if(!authors) {
throw new Error('Error')
}
return authors
}}
})
});
module.exports = PostType
- define
PostType
variable atsrc/graphql/queries/PostType.js
file, which contains theGraphQLObjectType
. GraphQLObjectType
has argumentObject
which contains:
{
name: `<Name of the GrapQLObjectType or Graphql Type>`,
description: `<Description of the GraphQLObjectType`>,
fields: () => `(function which contains return the fields of the GraphQLObjectType)`{
type: `<Datatype of the Fields>`
resolve: `(function for resolve the field from api or database or from ORM or ODM)`
}
}
- at
PostType
graphql object type, I have added theauthor
field andresolve
function to resolve the field information from mongodb database usingmongoose
model namedAuthor
model - I have use
async
function to resolve theauthor
field var authors = await Author.findById(mongoose.Types.ObjectId(post.author_id))
returnauthor
information related to specificpost
- to handle the error i have to throw the
Error
object, which object has noauthor
information bythrow new Error('Error')
- or
return authors
which containsAuthorType
graphql Object Type Information.
At the top level of every GraphQL server is a type that represents all of the possible entry points into the GraphQL API, it's often called the Root type or the Query type.!GraphQL Documentation
In this project I have create the root type at file src/graphql/queries/index.js
// src/graphql/queries/index.js
const { GraphQLList, GraphQLObjectType } = require('graphql');
const Author = require('../../models/Author')
const Post = require('../../models/Post')
const PostType = require('./PostType');
const AuthorType = require('./AuthorType')
const BlogQueryRootType = new GraphQLObjectType ({
name: 'BlogAppSchema',
description: "Blog Application Schema Query Root",
fields: () => ({
authors: {
type: new GraphQLList(AuthorType),
description: "List of all Authors",
resolve: async function () {
return await Author.find({}, (err, auth) => {
});
}
},
posts: {
type: new GraphQLList(PostType),
description: "List of all posts",
resolve: async function () {
var posts = await Post.find({})
return posts;
}
}
})
});
module.exports = BlogQueryRootType
BlogQueryRootType
is a root type and also aGraphQLObjectType
which contains all the attribute and functions like otherGraphQLObjectType
.- In
fileds
is functions which contains all theQuery
of the Schema. For Every GraphQLfield
has its ownresolve
options. authors
andposts
fieldsresolve
list of a thoseGraphQLObjectType
.
In REST, any request might end up causing some side-effects on the server, but by convention it's suggested that one doesn't use GET requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it's useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation.
Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update.!GraphQL Documentation
- In this project I have create the directory
src/graphql/mutations
. - Then I have create the file named
AuthorMutation.js
andPostMutation.js
fileimport
the graphql express functions and library andimport
mongoose
models for mutation operations.
// src/graphql/mutations/AuthorMutation.js
var {GraphQLNonNull, GraphQLString} = require('graphql');
var AuthorType = require('../queries/AuthorType');
var Author = require('../../models/Author')
- At
AuthorMutations.js
file have added the three functions for mutations.
// src/graphql/mutations/AuthorMutation.js
const addAuthor = {
type: AuthorType,
args: {
name: {
name: 'name',
type: new GraphQLNonNull(GraphQLString)
},
email: {
name: 'email',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async function (root, params) {
const uModel = new Author(params);
const newAuthor = await uModel.save();
if(!newAuthor) {
throw new Error('Error')
}
return newAuthor
}
}
- for
addAuthor
mutation type contains two attributes and oneresolve
function type
attribute value will be any type ofGraphQLObjectType
. I have addedAuthorType
graphql Object type foraddAuthor
mutation.args
attribute contains the paramter for this mutation. we can defind the each argument which contains:
<argument_name>: {
name: <argument_name> // Optional,
type: <GraphQL_type> // `GraphQLNonNull` for checking the null value
}
-
resolve
function using for resolve the mutations. which contains the all the functionality to change or effect themodels
ordata
. I have usemongoose
save
function to save the new author to mongodb database. also useasync
function to handle the mongodb operations. -
Mutation for update the author information, i write the code to
AuthorMutation.js
file. the function isupdateAuthor
. which contains similar attribute likeaddAuthor
:
// src/graphql/mutations/AuthorMutation.js
const updateAuthor = {
type: AuthorType,
args: {
_id: {
name: '_id',
type: new GraphQLNonNull(GraphQLString)
},
name: {
name: 'name',
type: GraphQLString
},
email: {
name: 'email',
type: GraphQLString
}
},
resolve: async function(root, param) {
let updateAuthor = {};
if(param.name) {
updateAuthor.name = param.name
}
if(param.email) {
updateAuthor.email = param.email
}
const uAuthor = await Author.findByIdAndUpdate(param._id, updateAuthor, {new: true})
console.log(uAuthor)
if(!uAuthor) {
throw new Error('Error')
}
return uAuthor
}
}
- For Delete author has similar attribute like
addAuthor
. the code snippet are shows below:
// src/graphql/mutations/AuthorMutation.js
const deleteAuthor = {
type: AuthorType,
args: {
_id: {
name: '_id',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async function (root, param) {
const deleteAuthor = await Author.findByIdAndRemove(param._id)
if(!deleteAuthor) {
throw new Error('Error');
}
return deleteAuthor
}
}
- then
export
those functions at end ofAuthorMutation.js
file:
// src/graphql/mutations/AuthorMutation.js
module.exports = {addAuthor, updateAuthor, deleteAuthor}
- Similar way i also create the
PostMutation.js
file to add themutation
functionality:
// src/graphql/mutations/PostMutation.js
var {GraphQLNonNull, GraphQLString} = require('graphql')
var Post = require('../../models/Post');
var PostType = require('../queries/PostType');
const createPost = {
type: PostType,
args: {
title: {
name: "title",
type: new GraphQLNonNull(GraphQLString)
},
author_id: {
name: "author_id",
type: new GraphQLNonNull(GraphQLString)
},
body: {
name: "body",
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async function(root, param) {
const postModel = new Post(param);
const savePost = await postModel.save();
if(!savePost) {
throw new Error('Error')
}
return savePost;
}
}
const updatePost = {
type: PostType,
args: {
_id: {
name: "_id",
type: new GraphQLNonNull(GraphQLString)
},
author_id: {
name: "author_id",
type: GraphQLString
},
title: {
name: "title",
type: GraphQLString
},
body: {
name: "body",
type: GraphQLString
}
},
resolve: async function (root, param) {
let updatePost = {};
if(param.author_id) {
updatePost.author_id = param.author_id;
}
if(param.title) {
updatePost.title = param.title
}
if(param.body) {
updatePost.body = param.body
}
const updatePostInfo = await Post.findByIdAndUpdate(param._id,updatePost,{new: true});
if(!updatePostInfo) {
throw new Error('Error');
}
return updatePostInfo;
}
}
const deletePost = {
type: PostType,
args: {
_id: {
name: "_id",
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async function (root, param) {
const deletePost = await Post.findByIdAndRemove(param._id);
if(deletePost) {
throw new Error('Error');
}
return deletePost;
}
}
module.exports = {createPost, updatePost, deletePost}
- then Create
index.js
file which will export all themutations
functions and objects
// src/graphql/mutations/index.js
var { addAuthor, updateAuthor, deleteAuthor } = require('./AuthorMutation');
var { createPost, updatePost, deletePost} = require('./PostMutation')
module.exports = {
addAuthor,
updateAuthor,
deleteAuthor,
createPost,
updatePost,
deletePost
}
Then I have create the schema
js file to src/graphql/schema.js
which contains all the graphql
Query
and Mutation
.
// src/graphql/schema.js
const { GraphQLObjectType,GraphQLSchema } = require('graphql');
const mutation = require('./graphql/mutations/index')
const BlogQueryRootType = require('./graphql/queries/index')
const BlogAppSchema = new GraphQLSchema({
query: BlogQueryRootType,
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: mutation
})
});
module.exports = BlogAppSchema;
GraphQLSchema
contains thequery
andmutation
attribute or object.query
attribute containsroot type
for graphql which i created atsrc/graphql/queries/index.js
file.mutation
attribute is also aGraphQLObjectType
, which contains two attribute.
{
name: <name of the mutation>
fields: <Main mutations Object>
}
- I have added the main
mutation
objects which are exported atsrc/graphql/mutations/index.js
graphql-express
which is a middleware of express and graphql.- check endpoint using
schema.js
schema atindex.js
root of the project directory.
// index.js
...
const schema = require('./src/schema');
...
...
app.use('/', graphQLHttp({
schema: schema,
graphiql: true
}));
- Run the Application by
npm start
or if you are usingyarn
command will beyarn start
- Mutation Example for Adding the Author
- Query Example for Show All Author
- Mutation for update Author Information
- Mutation for delete Author
- Mutation for create the post info
- Query for get all post with author data
- Update Post Information