Backend part with ExpressJS, GraphQL and MongoDB for a basic Todo app
danielpdev
Posted on March 3, 2019
Here is the live version on Glitch. (please make a remix before changing it)
Frontend part
Table of Contents
What is graphql?
A query language used to define an API which provides a complete and understandable description of the data and enables powerful developer tools.
More on Graphql.
Intro
This is the backend part of a basic TodoApp using ExpressJS and GraphQL.
Our Backend will use express-graphql combined with mongoose and for the server we will use ExpressJS.
To access the live version on Glitch.
Install prerequisites
Navigate to your projects directory and copy-paste the following commands:
mkdir todo-express-graphql && cd todo-express-graphql
npm install cors express express-graphql graphql mongoose
GraphQL types
cd todo-express-graphql && mkdir schema && cd schema && touch todo_type.js
TodoType
const mongoose = require('mongoose');
const graphql = require('graphql'); //package used to build our graphql schema
const {
GraphQLObjectType,
GraphQLID,
GraphQLInt,
GraphQLString
} = graphql; //necessary types for defining our schema
const TodoType = new GraphQLObjectType({
name: 'TodoType',
fields: () => ({
id: { type: GraphQLID },
likes: { type: GraphQLInt },
content: { type: GraphQLString },
})
});
module.exports = TodoType;
When we define a type for our GraphQL schema we need to create an instance of GraphQLObjectType
and pass an object with the required fields for our type.
name
is the only required field on a GraphQLObjectType
.
Some of the most commonly used properties that we will cover later in this post are fields
, needed to define the attributes that this type resolves to and resolve
function.
Please refer to official graphql documentation regarding GraphQLObjectType
RootQueryType
const mongoose = require('mongoose');
const graphql = require('graphql');
const {
GraphQLObjectType,
GraphQLList,
GraphQLID,
GraphQLNonNull
} = graphql;
const Todo = mongoose.model('todo');
const TodoType = require('./todo_type');
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
todos: {
type: new GraphQLList(TodoType),
resolve() {
return Todo.find({});
}
},
todo: {
type: TodoType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return Todo.findById(id);
}
}
})
});
module.exports = RootQueryType;
RootQueryType
has all the root endpoints needed for consuming our Todo resource. Here we are defining the todos
endpoint as a response which will contain a list of TodoType
documents by using GraphQLList(TodoType)
. Next is our todo
endpoint used for retrieving a
single todo from our database.
GraphQLNonNull
is used because we need to make sure that our id
received as query param is not undefined.
resolve(parentValue, { id })
first argument that resolve function receives is the parentValue or root which is the value passed down from other types. This argument enables the nested nature of GraphQL queries.
The second argument is the object with the actual query params.
What's nice about express-graphql is that always expects a promise to be returned from a resolver function and using mongoose will integrate
really smoothly.
More on resolvers from ApolloGraphQL documentation.
MutationType
Mutations are typically used to alter data from our database and you can see that they are very similar with our RootQueryType
, except that now we are altering data based on query params.
const graphql = require('graphql');
const { GraphQLObjectType, GraphQLString, GraphQLID } = graphql;
const mongoose = require('mongoose');
const Todo = mongoose.model('todo');
const TodoType = require('./todo_type');
const mutation = new GraphQLObjectType({
name: 'MutationType',
fields: {
addTodo: {
type: TodoType,
args: {
content: { type: GraphQLString }
},
resolve(parentValue, { content }) {
return (new Todo({ content })).save()
}
},
likeTodo: {
type: TodoType,
args: { id: { type: GraphQLID } },
resolve(parentValue, { id }) {
return Todo.like(id);
}
},
deleteTodo: {
type: TodoType,
args: { id: { type: GraphQLID } },
resolve(parentValue, { id }) {
return Todo.remove({ _id: id });
}
},
updateTodo: {
type: TodoType,
args: { id: { type: GraphQLID }, content: { type: GraphQLString } },
resolve(parentValue, { id, content }) {
return Todo.update({ _id: id }, { content });
}
},
}
});
module.exports = mutation;
Glueing code
const graphql = require('graphql');
const { GraphQLSchema } = graphql;
const query = require('./root_query_type');
const mutation = require('./mutations');
module.exports = new GraphQLSchema({
query,
mutation
});
Most of the times when you'll be writing your schema files you'll have to pass an object with two keys: query
and mutation
. Pretty simple and straightforward, just import the needed mutations and queries
and pass them as an object to GraphQLSchema
.
More on GraphQLSchema
Starting Express GraphQL server
const express = require('express');
const expressGraphQL = require('express-graphql');
const mongoose = require('mongoose');
const todoModel = require('./models/todo');
const bodyParser = require('body-parser');
const schema = require('./schema');
const cors = require('cors')
const app = express();
app.use(cors());
const MONGO_URI = 'your mLab link';
if (!MONGO_URI) {
throw new Error('You must provide a MongoLab URI');
}
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI);
mongoose.connection
.once('open', () => console.log('Connected to MongoLab instance.'))
.on('error', error => console.log('Error connecting to MongoLab:', error));
app.use(bodyParser.json());
app.use('/graphql', expressGraphQL({
schema, //pass the schema to our middleware
graphiql: true //enable graphiql interface so we can test our queries and mutations before starting to use it.
}));
app.get('/', (req, res) => {
res.redirect('/graphql');
});
app.listen(4000, () => {
console.log('Listening at 4000');
});
Testing queries and mutations
When you have to build a query and you don't know exactly how to write it
then graphiql
is going to help https://apollo-graphql-todo.glitch.me/graphql.
One of the powers of GraphQL is instant documentation. After we've defined the types that are going to be used in our GraphQLSchema
we have a documentation ready. Just access https://apollo-graphql-todo.glitch.me/graphql and on the top right you can find the Docs
.
Writing queries in graphiql:
query{
todos{
id
likes
content
}
}
This query is going to be run on our RootQueryType
and todos
field is going to be resolved to a list of TodoTypes
. TodoType
contains
an id
, likes
, content
as properties and because we have a list, we will get back a response that looks like this:
{
"data": {
"todos": [
{
"id": "5c5c21184c9edc006857c11b",
"likes": 17,
"content": ""
},
{
"id": "5c5c26e84c9edc006857c124",
"likes": 4,
"content": "asd"
},
{
"id": "5c5c29b296f75b0068f3b9db",
"likes": 0,
"content": "asdad"
},
{
"id": "5c5c29c296f75b0068f3b9dc",
"likes": 0,
"content": "eq123"
}
]
}
}
As an exercises, try to modify Add, Modify and Delete a Todo.
Conclusion
Express-graphql is a great tool for developing backends that need to support GraphQL and now we've seen how easily it can be integrated with MongoDB. We now have a small example of how you could implement some basic queries and mutations.
I hope you have enjoyed this article.
Posted on March 3, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.