Creating a simple CRUD app with NodeJS, GraphQL and MongoDB + Docker
Mohamed Aymen Ourabi
Senior Frontend Engineer | React | Angular | Node Js | Typescript
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)
Technical Lead Java-Angular / Consultant technico-fonctionnel
6 年Très bel article
Cloud & DevOps Engineer at Alpian
6 年DJANFAR AHAMADA
?? AI Whisperer ??
6 年Very nice article.