DEV Community

danielpdev
danielpdev

Posted on

Backend part with ExpressJS, GraphQL and MongoDB for a basic Todo app

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.

Latest comments (1)

Collapse
 
moatazabdalmageed profile image
Moataz Mohammady

Thank you for awesome tutorial