Creating a simple CRUD app with NodeJS, GraphQL and MongoDB + Docker

Creating a simple CRUD app with NodeJS, GraphQL and MongoDB + Docker


Introduction

When it comes to the development of modern web applications we usually talk about network requests between client and server applications. In fact REST web architecture was the most popular and used style in the development of backend webservices.

In REST everything is about resources , you can create , update , delete , or fetch data using different kind of endpoints that you create on your server side and since Http protocol is used in REST for comunication we have to perform multiple requests to every single endpoint to manipulate our data .

With all these features REST was the status for many years in the world of web development but another technology developed by Facebook appeared in the last few years: it’s called GraphQL which came with a new approach that reduces all your entry points into one entry point.

In this article we will try to understand this architecture by creating a simple CRUD with Node js , GraphQL and MongoDB

What the hell is GraphQL ?


GraphQL is an open source query language created by Facebook which was developed as an alternative to REST architecture. As a client you are in charge of requesting data otherwise you can ask exactly for what you need no more no less . When having a RESTful architecture, this becomes quite difficult, because the backend defines what is available with each resource on each URL. It is not the frontend which manipulate the selection of data so that it always has to request all the information from an endpoint even when it only needs little part of it.

With GraphQL no more GET , POST ,PUT ? what ? how ?

Unlike REST GraphQL have a query language which is slightly more expressive and abstracted than REST endpoints.

This is what a graphQL query looks like

query:{

  book(id:57776){

     author
   }
     
 }

and this is what a response looks like

{

 "book":{

     "author": "Victor Hugo" 
   }
     
 }

In this exemple we try to fetch the author of the book which has the id 57776. Instead of making a request to the endpoint (using some kind of Url like this "https://localhost:3000/api/books/57776" which render a book object with all the properties) you can ask with graphQL to only fech and return the name of the author.


Getting started

Before we get start you should have node and npm installed on your machine to check out open your command line and write npm -v and node -v.

Note that for this tutorial we will run our project under a docker container so you have also to install docker and docker-compose

to start open your command line and type this command

$ npm init -y

this will create a package.json file.

Next add the dependencies below to package.json in the section dependencies

  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.6.12",
    "graphql": "^14.0.2",
    "mongoose": "^5.3.5"
  }

when looking to our package.json we can see that we require the following dependencies :

express-graphql : The express-graphql dependency provides a simple implementation of an Express server that runs GraphQL API.

graphql: The JavaScript reference implementation for GraphQL

mongoose : an Object Data Modeling (ODM) library for MongoDB and Node.js.

Now create a file called server.js and add the code below

const express = require('express')
const app = express()
const mongoose = require('mongoose');


//connecting to mongodb

mongoose.connect('mongodb://mongo/myappdb',(err)=>{
    if (err) throw err;
    console.log("connected to mongo");
})  


app.set('port', (process.env.PORT || 4000));

app.listen(app.get('port'), ()=> {

    console.log("Node app is running at localhost:" + app.get('port'))
});

next create a Dockerfile and add these few lines

#define the latest nodejs image  to build from
FROM node:latest
#create a working directory
WORKDIR /usr/src/app/graphqlApp
#copy package.json file under the working directory 
COPY package.json /usr/src/app/graphqlApp/
# install all the dependencies 
RUN npm install
#copy all your files under the working directory
COPY . /usr/src/graphqlApp
#expose the port 4000
EXPOSE 4000
#start nodejs server 
CMD npm start

For mongoDB we will use the official image from DockerHub registery and to take a better look to our database we will require another image called admin-mongo

Let’s add a docker-compose.yml file to define the services in our application.

version: "2"

services:
#define a service called app
  app:
#adding a container name
    container_name: graphqlapp
#restart the container automatically if it fails
    restart: always
#building the app image using the Dockerfile in the current directory
    build: .
#mapping the host port to the container port.
    ports:
      - "4000:4000"
    links:
      - mongo
#create another service called mongo
  mongo:
    container_name: graphqlmongo
#pull the official mongodb image from DockerHub registry
    image: mongo
#mount the host directory for persistent storage
    volumes:
      - ./data:/data/db
    ports:
      - "27017:27017"
#creating a service called admin-mongo
  admin-mongo:
#pull down the official image from DockerHub registry
    image: 0x59/admin-mongo:latest
    ports:
      - "8082:8082"
    environment:
      - PORT=8082
      - CONN_NAME=mongo
      - DB_HOST=mongo
    links:
      - mongo

Done with docker , let's go back to our Graphql stuff !!

Since we are going to work with mongoDB , the first thing we need to do is to create a Schema that define our object .

To do that create a file called Book.js next add the following code

var mongoose = require('mongoose');
var Schema = mongoose.Schema;


var BookSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  author:{
    type:String,
    required:true
  }


});
var Model = mongoose.model('book', BookSchema);
module.exports = Model;

First thing we have to do for graphql is to define what we call a GraphQL type that defines the types for the graphQL Api , in fact GraphQl supports different kind of types like String , boolean , ID etc

to create a graphql type for our book add a file called BookType.js , after that add the code below

var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLID = require('graphql').GraphQLID;
var GraphQLString = require('graphql').GraphQLString;



exports.bookType = new GraphQLObjectType({
  name: 'book',
  fields:  () =>{
    return {
      id: {
        type: new GraphQLNonNull(GraphQLID)},      
        name: {        type: GraphQLString      },     
       author: {        type: GraphQLString      }    

      }
  
    }});

Done with our bookType we can move now to the CRUD operations .

In graphQL operations can be divded into 3 types

  • Query (Read)
  • Mutation (Create,Update,Delete)
  • Subscription (continuous read)

in order to make the read operation create a file called BookQuery.js

BookQuery.js

var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLList = require('graphql').GraphQLList;
//import book model 
var BookModel = require('./book');
//import GraphQL BookType
var bookType = require('./bookType').bookType;




// Query
exports.BookQuery = new GraphQLObjectType({
  name: 'Query',
  fields:  ()=> {
    return {
      books: {
        type: new GraphQLList(bookType),
        resolve:  async ()=> {
          const books = await BookModel.find()
          if (!books) {
            throw new Error('error while fetching data')
          }
          return books
        }
      }
    }
  }
})

Now let's create our mutations

First create 4 files BookMutation.js ,AddBook.js,UpdateBook, DeleteBook and add the few lines below to each file.

AddBook.js

var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLString = require('graphql').GraphQLString;
var bookType = require('./bookType');
var bookModel = require('./book');
exports.addBook = {
  type: bookType.bookType,
/* define the arguments that we should pass to the mutation
   here we require both book name and the author name 
   the id will be generated automatically 
*/
  args: {
    name: {
      type: new GraphQLNonNull(GraphQLString),
    },
    author: {
        type: new GraphQLNonNull(GraphQLString),
      }
  },
  resolve: async(root, args)=> {

 //under the resolve method we get our arguments
  
    const uModel = new bookModel(args);
    const newBook = await uModel.save();
    if (!newBook) {
      throw new Error('error');
    }
    return newBook
  }
}

UpdateBook.js

var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLString = require('graphql').GraphQLString;
var bookType = require('./bookType');
var bookModel = require('./book');


exports.updateBook = {
    type: bookType.bookType,
    args: {
        id: {

            type: new GraphQLNonNull(GraphQLString)
        },
        name: {
            type: new GraphQLNonNull(GraphQLString),
        },
        author: {
            type: new GraphQLNonNull(GraphQLString),
        }
    },
    resolve: async(root, args) =>{

        const UpdatedBook = await bookModel.findByIdAndUpdate(args.id,args);
        if (!UpdatedBook) {
          throw new Error('Error')
        }
        return UpdatedBook;
    }
}

DeleteBook.js

var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLString = require('graphql').GraphQLString;
var bookType = require('/bookType');
var bookModel = require('./book');


exports.remove = {
  type: bookType.bookType,
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve: async(root, args)=> {
    const removedBook = await bookModel.findByIdAndRemove(args.id)
    if (!removedBook) {
      throw new Error('error')
    }
    return removedBook;
  }
}

BookMutations.js

var addbook = require('./AddBook').addBook
var updatebook = require('./UpdateBook').updateBook
var deletebook = require('./DeleteBook').removeBook

module.exports = {
  addbook,
  updatebook,
  deletebook
}

Finally we will create the BookSchema in which we will add the queries along side with the mutations .

In order to do that create a file called BookSchema.js then import BookMutations and BookQuery using require() method

BookSchema.js

var GraphQLSchema = require('graphql').GraphQLSchema;
var GraphQLObjectType = require('graphql').GraphQLObjectType;
var query = require('./BookQuery').BookQuery;
var mutation = require('./BookMutation')


exports.BookSchema = new GraphQLSchema({
  query: query,
  mutation: new GraphQLObjectType({
    name: 'Mutation',
    fields: mutation
  })
})

after setting up all the configuration for our graphql schema along side with all the operations, we have to add a few lines to our main file server.js to run the GraphiQL interface and try to execute some queries .

in server.js file import the BookSchema and add the code below

// import graphql-express and BookSchema
const graphqlExpress = require("express-graphql");
const bookSchema = require('./BookSchema').BookSchema;

//add the schema to graphql-express 

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

the final Server.js file should look like this

const express = require('express')
const app = express()
const mongoose = require('mongoose');
const graphqlExpress = require("express-graphql");
const bookSchema = require('./graphql/BookSchema').BookSchema;


mongoose.connect('mongodb://mongo/myappdb', (err) => {
    if (err) throw err;
    console.log("connected to mongo");
})





app.set('port', (process.env.PORT || 4000));

app.listen(app.get('port'),  () =>{
    console.log("Node app is running at localhost:" + app.get('port'))
});




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

now with eveything setup let's build and run the project .

to do that open your terminal under your project directory and tape

$ sudo docker-compose build

when it's complete run the project using

$ sudo docker-compose up 


your node js project will run on https://localhost:4000/graphql

this what a graphiql interface look like . You can make all the queries that you want and try different kind of schema that you have defined.

Let's execute our first mutation and add a book in our database

to check whether the data is persisted to mongodb or not open admin mongo interface in your browser on https://localhost:8082/

to create a connection with our database add a random name that you choose for the connection and after that add this connection string to the connection string textfield (mongodb://mongo/myappdb)

we can see here that a collection called books is created .

we can see also that the document is created and everything is fine

let's return to our nodejs app and try the execute other operations.

Update :

Updated successfully !

Delete :

Now let's try to fetch some data using the query that we have defined

as we can see in both screenshots we can request any type of data that we want to fetch. If we need only book names we will get only names if we ask for the authors we will get only authors and by the way if we need an entire object we will get it as well .

this is where GraphQL is powerfull than REST . We as a client can take control of any type of data that we want to fetch .

No need to make any updates in the server side we only get what we want no more no less.

Conclusion

In this tutorial we had a great look at graphql and the main features that includes note that there are many other tools that are included and could be used to make very beautifull api's.

you can find the project on my github repo :( https://github.com/medaymenTN/NodeJsGraphQLDockerApp)

Bechir Medini

Technical Lead Java-Angular / Consultant technico-fonctionnel

6 年

Très bel article

Amir Khan

?? AI Whisperer ??

6 年

Very nice article.

要查看或添加评论,请登录

Mohamed Aymen Ourabi的更多文章

社区洞察

其他会员也浏览了