GraphQL with Express, MongoDB

Htoo Pyae Lwin
13 min readDec 13, 2018

Let’s get started with GraphQL by building a fully functional CRUD API including authentication for a blog app with GraphQL, Express, MongoDB and Passport JWT. You should have basic Node.js, Express.js and MongoDB skill to follow along.

What we’re gonna build is a blog but we’re gonna skip front-end stuff since our focus is GraphQL. So, about our app — it will have posts, comments associated with a post and users. Each post gonna have tags which help indicates what a specific post is about. So, without farther ado, let’s jump right in.

Photo by Markus Spiske on Unsplash

Setting up the project

First, we will create our project folder

$ mkdir graphql-blog && cd graphql-blog

Now, we are in our project directory and we will create a package.json file. You can just press enter for all questions

$ npm init

Then, we will install all of our dependencies.

$ npm i express mongoose graphql express-graphql

Having successfully installed, we can now continue to implement our GraphQL server. But before that, let’s run a couple of commands in our terminal

$ git init
$ echo node_modules/ >> .gitignore
$ echo config/ >> .gitignore

Creating a Server with Express

First, let’s open up the editor of our choice and we will create a file named app.js. In our app.js, first, we will import the dependencies and start our server.

const express = require('express');
const graphqlHTTP = require('express-graphql');
const app = express();app.listen(process.env.PORT || 4000, () => {
console.log(`Server started on http://localhost:4000`);
});

Starting the Server with Nodemon

For convenience, we’ll use nodemon and if you happen to do not know what nodemon is, it is a tool that watches changes to our files and will automatically restart the server for us so that we don’t have to rerun the server every time we make a change to our files. To do so, in our terminal —

$ nodemon app.js

Let it run throughout our project development.

Creating a GraphQL Server

Now that the server is running, we will add below code to app.js.

app.use('/graphql', graphqlHTTP({
// schema,
// rootValue,
graphiql: true
}));

graphiql is a tool for interacting with our GraphQL API and we should use this tool only for development purposes.

Now if you go to this URL http://localhost:4000/graphql, you’re gonna see this error —

This is because we haven’t implemented our GraphQL schema yet, so let’s create one.

Creating a basic GraphQL Schema

Create a new folder called graphql and in that directory, create two files called schema.js and resolver.js. In schema.js, we will define our type definitions —

const { buildSchema } = require('graphql');const schema = buildSchema(`
// our type definitions
`);
module.exports = schema;

If you are not sure about how GraphQL works, here is a little bit about it. GraphQL has a type system for defining how your data looks like and each type has fields which are just data fields such as name, age etc. GraphQL also has functions called resolvers for providing data of respective field and type you define. For example, a custom type for a user may look like this —

type User {
id: ID!
name: String
active: Boolean
age: Int
}

The exclamation mark ( ! ) indicates that this field cannot be null. GraphQL only has one endpoint, in this case, /graphql as we defined earlier, so we always have to specify the root query —

type Query {
users: [User!]!
}

In the above root query, we are retrieving all users, so to break it down — users is the name of our query and it will return an array of User type, which we defined above. So, we might get something like this —

{
"data": {
"users": [
{
"id": 1,
"name": "Tony",
"active": true,
"age": 30
},
{
"id": 2,
"name": "Steve",
"active": false,
"age": 100
}
]
}
}

As you can see, the return data is pretty similar to what we asked for in our query.

Defining types for Post, Comment, Tag, and User

Back in our schema.js file, we will now write our app’s type definitions

const { buildSchema } = require('graphql');const schema = buildSchema(`
type Query {
posts: [Post!]!
post(id: ID!): Post!
tags: [Tag!]!
tag: Tag!
}
type Post {
id: ID!
title: String!
body: String!
comments: [Comment!]!
tags: [Tag!]!
owner: User!
}
type Comment {
id: ID!
comment: String!
owner: User!
}
type Tag {
id: ID!
tag: String!
}
type User {
id: ID!
name: String
about: String
email: String
}
`);
module.exports = schema;

Defining resolver functions for each field

As for now, we will work with dummy data to test our GraphQL service. In resolver.js

const tags = [
{
id: 1,
tag: 'GraphQL'
},
{
id: 2,
tag: 'Node.js'
},
{
id: 3,
tag: 'Express.js'
}
];
const posts = [
{
id: 1,
title: 'Post 1',
body: 'This is the body of post 1 ...',
},
{
id: 2,
title: 'Post 2',
body: 'This is the body of post 2 ...',
}
];
const resolver = {
// Queries
// get all posts
posts: () => {
return posts;
},
// get a specific post based on id
post: (args) => {
return posts.filter(p => p.id == args.id)[0];
},
// get all tags
tags: () => {
return tags;
},
// get a specific tag based on id
tag: (args) => {
return tags.filter(t => t.id == args.id)[0];
}
};
module.exports = resolver;

There are other parameters besides args in each resolver function and args is an object of arguments we passed in our Root Query.

Testing things so far

Let’s include the above two files in our app.js,

...
const schema = require('./graphql/schema');
const resolver = require('./graphql/resolver');
app.use('/graphql', graphqlHTTP({
schema,
rootValue: resolver,
graphiql: true
});
...

Now, if we go to http://localhost:4000/graphql again, you will see GraphiQL interface —

If you click the Docs at the top right corner, you will see the documentation of our GraphQL schema. It shows us our Root Query too, so if we click it, you will see the fields of our Root Query.

So, let’s test it out

As you can see, our GraphQL is working and we will now begin to add interaction with MongoDB with Mongoose. But before that, let’s commit our changes

$ git add -A
$ git commit -m 'GraphQL ok'

Creating Mongoose Models

In our project directory, we will create a new folder called models and inside that folder, we will create three new files — post.js, tag.js, user.js. Now, our project directory structure should look like this —

In user.js,

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
default: ''
},
about: {
type: String,
default: ''
}
});
module.exports = mongoose.model('User', userSchema);

In tag.js,

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const tagSchema = new Schema({
tag: {
type: String,
required: true
}
});
module.exports = mongoose.model('Tag', tagSchema);

In post.js,

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const commentSchema = new Schema({
comment: {
type: String,
required: true
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
const postSchema = new Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true
},
comments: [commentSchema],
tags: [{
type: Schema.Types.ObjectId,
ref: 'Tag'
}],
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Post', postSchema);

Pheww, just hold on, we’re getting there. If you have MongoDB locally installed on your machine, that’s great. In your terminal, just run

$ mongod

and copy your database URL. But if you don’t, we’re gonna use MLab which is a Database-as-a-Service for MongoDB. Go to https://mlab.com/, create an account, database and also create a database user. You should remember your database user credentials and copy your database URL which looks something like this —

mongodb://<dbuser>:<dbpassword>@ds129972.mlab.com:12456/graphql-blog

Connecting to MongoDB

First of all, let’s create a new file in our project directory by running this command —

$ mkdir config && touch config/keys.js

Open keys.js file, export your database URL and save it.

module.exports = {
MONGO_URI: 'mongodb://blog:blog123@ds129972.mlab.com:12456/graphql-blog'
};

Then in our app.js,

...
const graphQLHTTP = require(‘express-graphql’);
const mongoose = require(‘mongoose’);
const config = require(‘./config/key');
...mongoose.connect(config.MONGO_URI, { useNewUrlParser: true }, (err) => {
if (err)
console.log(err.message)
else
console.log('MongoDB Successfully Connected ...');
});
...

In your terminal, you should see

Integrating GraphQL with MongoDB

Having successfully connected to our database, we can now go back to resolver.js where our resolver functions reside

In resolver.js, we will remove our dummy data, import mongoose models and make use of them.

const Post = require('../models/post');
const Tag = require('../models/tag');
const resolver = {
// Queries
// get all posts
posts: () => {
return Post.find().populate('owner tags comments.owner').then(posts => posts).catch(err => err);
},
// get a specific post based on id
post: (args) => {
return Post.findById(args.id).populate('owner tags comments.owner').then(post => post).catch(err => err);
},
// get all tags
tags: () => {
return Tag.find().then(tags => tags).catch(err => err);
},
// get a specific tag based on id
tag: (args) => {
return Tag.findById(args.id).then(tag => tag).catch(err => err);
}
// Mutations
};
module.exports = resolver;

You can see that we’re using mongoose population so that we can reference Tag and User models. Now if we go back to our browser, visit http://localhost:4000/graphql and write a query like below —

it is returning an empty array and it’s fine since we have no data in our database. That’s why we’re gonna cover Mutation which is just making changes to our database through GraphQL, but before that, let’s integrate authentication with Passport JWT which we’re definitely gonna need in our blog app.

Authentication with Passport JWT

First, let’s stop our nodemon process and let’s install a couple of dependencies to integrate authentication into our app.

$ npm i jsonwebtoken passport passport-jwt passport-local passport-local-mongoose

We will use both Local Strategy and JWT Strategy for our app and now we can rerun our server with

$ nodemon app.js

First of all, we will edit our models/user.js so that we can use passport and mongoose together for local strategy. We will override the default username and password pair to email and password pair.

...
const passportLocalMongoose = require('passport-local-mongoose');
const userSchema = new Schema({
name: {
type: String,
default: ''
},
about: {
type: String,
default: ''
}
});
userSchema.plugin(passportLocalMongoose, {
usernameField: 'email'
});
...

We’re gonna open up config/keys.js and add our secret key, so in keys.js

module.exports = {
MONGO_URI: 'Your Mongo URI here ...',
SECRET_KEY: 'graphql-12345'
};

Then, we’re gonna need to create a new file in our project root directory and name it as authenticate.js which will contain our authentication logic.

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwt = require('jsonwebtoken');
const User = require('./models/user');
const config = require('./config/keys');
// local strategy
exports.local = passport.use(new LocalStrategy({ usernameField: 'email' }, User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// JWT strategy
exports.generateToken = (user) => {
return jwt.sign(user, config.SECRET_KEY, { expiresIn: 86400 });
};
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.SECRET_KEY
};
exports.jwtStrategy = passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
if (err)
return done(err, false);
else if (user)
return done(null, user);
else
return done(null, false);
}));
// verifying user
exports.verifyUser = passport.authenticate('jwt', { session: false });

Just to explain this quickly, we are using Local Strategy with the email-password pair, we generate JWT token which will expire after 24 hours by signing user information with the secret key we defined in keys.js, we define JWT Strategy with options such as token extraction as Bearer token and lastly, we export a function which will be used to verify a user’s authenticity.

After that, we will import passport and our authenticate.js and initialize passport in app.js —

...
const passport = require('passport');
const authenticate = require('./authenticate');
const User = require('./models/user');
...
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use('/graphql', authenticate.verifyUser, graphqlHTTP({
schema,
rootValue: resolver,
graphiql: true
}));
...

Now, if we go to our app URL —

We will no longer be able to access since we have our authentication middleware implemented. We need to authenticate ourselves to use the app and to do so, we’re gonna implement signup and login routes, In app.js, just above our /graphql —

...
app.use(passport.initialize());
app.post('/signup', (req, res) => {
User.register(new User({ email: req.body.email }), req.body.password, (err, user) => {
if (err) {
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json');
res.json({ err: err });
}
else {
passport.authenticate('local')(req, res, () => {
const token = authenticate.generateToken({ _id: req.user._id });
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({ token: token, status: 'Successfully Logged In' });
});
}
});
});
app.post('/login', passport.authenticate('local'), (req, res) => {
const token = authenticate.generateToken({ _id: req.user._id });
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({ token: token, status: 'Successfully Logged In' });
});
app.use('/graphql', authenticate.verifyUser, graphqlHTTP({
schema,
rootValue: resolver
}));
...

You may notice that we remove graphiql: true, cuz we can’t directly use authorization token in GraphiQL, and that’s why we’re gonna use GraphQL Playground which is an IDE for better development. Now, we will close the browser tab of our app URL and gonna open up Postman to sign up our first user.

Oh yay, you can see that the token is generated and if we go to our MLab database

Moreover, we gonna open up GraphQL Playground, copy our token from Postman visit our app URL and write some queries.

Let’s commit our changes

$ git add -A
$ git commit -m 'Auth with JWT OK'

GraphQL Mutation

We will now try to make changes to our database through GraphQL queries. To do so, in schema.js

...
type Mutation {
addPost(title: String!, body: String!): Post!
updatePost(id: ID!, title: String, body: String): Post!
deletePost(id: ID!): Post!
addPostTag(postId: ID!, tagId: ID!): Post!
removePostTag(postId: ID!, tagId: ID!): Post!
addComment(postId: ID!, comment: String!): Post!
updateComment(postId: ID!, comid: ID!, comment: String!): Post!
deleteComment(postId: ID!, comid: ID!): Post!
createTag(tag: String!): Tag!
}
...

We will resolve these fields in resolver.js

...
// Mutations
addPost: (args, context) => {
args.owner = context.user._id;
return Post.create(args).then(post => post).catch(err => err);
},
updatePost: (args, context) => {
return Post.findById(args.id)
.then(post => {
if (post.owner == context.user._id.toString())
return Post.findOneAndUpdate({_id: args.id}, args, { new: true });
})
.catch(err => err);
},
deletePost: (args, context) => {
return Post.findById(args.id)
.then(post => {
if (post.owner == context.user._id.toString())
return Post.findOneAndDelete({_id: args.id});
})
.catch(err => err);
},
addPostTag: (args, context) => {
return Post.findById(args.postId)
.then(post => {
if (post) {
if (post.owner == context.user._id.toString()) {
post.tags.push(args.tagId);
return post.save();
}
}
})
.catch(err => err);
},
removePostTag: (args, context) => {
return Post.findById(args.postId)
.then(post => {
if (post) {
if (post.owner == context.user._id.toString()) {
post.tags.splice(post.tags.indexOf(args.tagId), 1);
return post.save();
}
}
})
.catch(err => err);
},
addComment: (args, context) => {
return Post.findById(args.postId)
.then(post => {
if (post) {
post.comments.push({
comment: args.comment,
owner: context.user._id
});
return post.save();
}
})
.catch(err => err);
},
updateComment: (args, context) => {
return Post.findById(args.postId)
.then(post => {
if (post) {
if (post.comments.id(args.comId).owner == context.user._id.toString()) {
post.comments.id(args.comId).set({ comment: args.comment });
return post.save();
}
}
})
.catch(err => err);
},
deleteComment: (args, context) => {
return Post.findById(args.postId)
.then(post => {
if (post) {
if (post.comments.id(args.comId).owner == context.user._id.toString()) {
post.comments.id(args.comId).remove();
return post.save();
}
}
})
.catch(err => err);
},
createTag: (args) => {
return Tag.create(args).then(tag => tag).catch(err => err);
}
...

As I mentioned earlier, there are other parameters besides args, and here we use context which holds authenticated user information. Finally, our GraphQL server is ready to be tested. Let’s go to Postman and log yourself in, copy token and go to GraphQL Playground again, paste your token in HTTP Headers. Now, let’s create a new post.

a new post is successfully created, well that’s great. Let’s add a comment and we will do so by logging ourselves as another user

Great! Now, let’s create a new tag

and we will tag it to our post.

Finally, let’s retrieve all posts and see how it looks —

That’s it! Kudos to you. We’ve successfully created a fully functional GraphQL API and if you want, you can test others functions as well.

Conclusion

Thanks for sticking with me till the end and I hope you get the idea of how GraphQL works with Express.js and MongoDB as what I intended to show. You can see the complete code for this project here. This is my very first post on medium and I’m trying to write more about web development and computer science related posts, so if you find the above content helpful, pls click the clap button.

--

--