Dockerizing a Node, Express and MongoDB Application

In this tutorial, we will dockerize a Node,Express and Mongo Application. We will be using a Docker Linux container.

Let’s first take an overview of Docker and then we will directly jump to the implementation of the application.

What is Docker and why to use it?


When our applications are ready for  deployment, we have to configure a production machine with all the dependencies for the application. This is often a tedious and time consuming job. To make this approach easy, we can make use of Docker.

Docker provides a way to run applications securely and isolated in a container with all the applications dependencies packaged in it. Docker containers wrap up software and its dependencies into a standardized unit for software development. This makes sure that all the dependencies to run the application are packaged so that application will always run.

Some additional advantages of using Docker are:
  1. Once a docker image is created with all its dependencies, you need not worry about the operating system configuration for your application deployment. Since Docker is platform independent, a docker image that has successfully build  can run on any platform using docker.
  2. This docker image can be easily shared with a client (or anyone) who wants to use the application. They can run the application simply by installing docker on their machine.        
Docker can be downloaded from this link.


The dockerized application will be as arranged as shown in Figure 1.

   docker-structure

Figure 1: The structure of the application dockerization

Creating the Node, Express and MongoDB Application

The code for this application is implemented using Microsoft Visual Studio Code (VS Code). It can be downloaded from this link.

We Also need to install Node.js. It can be downloaded from this link. We will create a REST API using Express.js. This will perform GET and POST operations using MongoDB Collection.

Note that, this article concentrates on dockerizing Node.js, Express.js and MongoDB application. This application does not explains Node.js and Express. If you want to read basics of Node and Express then please visit:

Step 1: Create a folder and name it as men_docker. In this folder add a new folder and name it as dockernodeapp. Open the men_docker folder in VSCode. Open Node.js command prompt as Administrator (Note that I am using Windows 10 machine for this application). In the command prompt, navigate to the dockernodeapp folder and run the following command to create a package.json file

npm init –y

This command will create a package.json file. Modify this file as shown in the following code listing
{
  "name": "dockernodeapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/node": "10.12.18",
    "body-parser": "1.18.3",
    "cors": "^2.8.5",
    "express": "4.16.4",
    "mongoose": "^5.4.6"
  }
}


Listing 1: package.json with all dependencies
 
Note that the scripts block in the above package.json has start command. This will run the server.js. We will create this file in the next step.

Step 2: In the dockernodeapp folder, add a new JavaScript file. Name this file as server.js. Add the following code in this file
// 1. load all required packages for the application
var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongoose = require("mongoose");
mongoose.Promise = global.Promise;
var cors = require("cors");
// 2. router, body parser and cors
var instance = express();
var router = express.Router();
instance.use(router);
instance.use(bodyParser.urlencoded({
  extended: false
}));
instance.use(bodyParser.json());
instance.use(cors());
// 3. connect to mongodb. since we will be configuring
// mongodb on docker image, the port must be specified.
// This will map from the docker-compose.yml file 
mongoose.connect(
  "mongodb://db:27017/ProductsAppsDb", {
    useNewUrlParser: true
  }
);

// 4. connection with the mongodb database
var dbConnect = mongoose.connection;
if (!dbConnect) {
  console.log("Sorry Connection is not established");
  return;
}

//  5. schema for the collection
var productsSchema = mongoose.Schema({
  ProductId: Number,
  ProductName: String,
  CategoryName: String,
  Manufacturer: String,
  Price: Number
});
// 6 mapping with the collection
var productModel = mongoose.model("Products", productsSchema, "Products");
// 7. REST Apis for get/post
instance.get("/api/products", function (request, response) {
  productModel.find().exec(function (err, res) {
    if (err) {
      response.statusCode = 500;
      response.send({
        status: response.statusCode,
        error: err
      });
    }
    response.send({
      status: 200,
      data: res
    });
  });
});

instance.post("/api/products", function (request, response) {
  // parsing posted data into JSON
  var prd = {
    ProductId: request.body.ProductId,
    ProductName: request.body.ProductName,
    CategoryName: request.body.CategoryName,
    Manufacturer: request.body.Manufacturer,
    Price: request.body.Price
  };
  // pass the parsed object to "create()" method
  productModel.create(prd, function (err, res) {
    if (err) {
      response.statusCode = 500;
      response.send(err);
    }
    response.send({
      status: 200,
      data: res
    });
  });
});
// 8. listen on the port
instance.listen(4007, function () {
  console.log("started listening on port 4007");
});

Listing 2: The Express Code for implementing REST API and perform GET and POST operations against MongoDB database

The above code does the following (note that following line numbers matches with comment numbers given to code.)
  1. Since we will be using Express, MongoDB, Body-Parser and Mongoose for application implementation, we need to load these modules in the application.
  2. This step defines an express instance and configures body-parser object to it so that Http Post operation can be implemented.
  3. This step will connect to MongoDB database. Since we will be configuring the MongoDB database image,  we need to make sure that our application should connect to the MongoDB database instance on the port 27017. In the connect() method we have mentioned the MongoDB Connection string as mongodb://db:27017/ProductsAppsDb. The db  here is the MongoDB image service which represents the MongoDB database instance running and accessible on the port 27017.
  4. This steps make sure that the connection with the database is established successfully.
  5. Once the database is created, we need the collection in it. This step defines the expected schema for the collection.
  6. This step will map the schema with the MongoDB collection. If the Products collection is not already present then it will be created.
  7. This step defines the use of express methods for performing Http GET and POST
  8. This step will start listening on port 4007
Step 3: In the dockernodeapp folder, add a new file and name it as dockerfile. In this file, we need to configure all dependencies needed for Node and Express application. The following listing shows the docker file:
FROM node:10.15.0
# 1. Creating the app directory
WORKDIR /usr/src/app
# 2. Install app dependencies for the application
# A package*.json is used to make sure the 
# package.json, package-lock.json are to be copied
COPY package*.json ./
# 3 RUN npm install --only=production for production
RUN npm install --only=production
# Bundle the app source
COPY . .
EXPOSE 4007
CMD [ "npm", "start" ]
Listing 3: docker file

The dockerfile has the following specifications:
  • Since we need an image with node.js installed on it we specify node.js version.
  • we are setting working directory as /user/src/app. This will be working directory for all our commands.
  • copy package.json and package-lock.json file in the working directory. The package.json contains list of all dependencies for the application.
  • run the npm install –only=production. This will install all dependencies for the application.
  • bundle application source as the original folder structure of the application using COPY . . command.
  • Using EXPOSE command we are specifying that the port 4007 will be exposed from the image for the application.
  • Using CMD command we are specifying that the npm start command to run server.js from the image so that the application will start listening on 4007.    
  Add the .dockerignore file in the dockernodeapp folder and add the following lines to it:
node_modules
npm-debug.log 
This will make sure that, node_module folder and npm-debug.log file will not be copied in the image.
 
Step 4: Run the following command from the node.js command prompt. This will build the docker image for the application

docker build -t nodeexpressmongo:prod .

The –t switch is used to tag the image. This command will create and build an image as shown in the following image.
(c) DevCurry.com
  
Figure 2: Downloading all dependencies
The above image shows that the required dependencies will be downloaded. Once the download is completed, the result will be shown in the following image:
  
Figure 3: The final step of the image build
To run the image run the following command

docker run -it -v :/usr/src/app -v /usr/src/app/node_modules -p 4007:4007 --rm nodeexpressmongo:prod

We are trying to run the image and try to map the port 4007 of the image with the port 4007 of the local machine.

The above command will show the following result:
  
Figure 4: Running the image

The image is running and it invokes node server.js command and listens on port 4007, but since the server.js file contains code for connecting to MongoDB database on port 27017, it throws MongoNteworkError.
So this means that we need to make sure that MongoDB must be configured on the image. But we cannot specify MongoDB image using dockerfile because it is already present on the Docker Hub. This MongoDB image has its own container. So now the challenge is how to connect our image with the MongoDB’s already available image in its container?
We can make this possible using Docker compose.

Docker Compose  

Compose is a tool for defining and running multi-container Docker applications. We need to to create docker-compose.yml file. This compose file can be used to configure an application services, this file will execute dockerfile for individual application service and then download dependencies for image or pull already available image.

Step 5: In the men_docker folder, add a new file and name it as docker-compose.yml. In this file, add the following configuration:

# 1. specify docker-compose version
version: "3.0"

# 2. Defining the application containers to be run
services:
  #  2a. name of the application
  express:
    # 2b. specify the directory of the application containing of the Dockerfile
    build: dockernodeapp
    ports:
      # 2c. specify ports mapping
      - "4007:4007"
    links:
      # 2d. link this service to the database service
      - db
  # 2e. name of the database service
  db:
    # 2f. specify image to build container from mongo
    image: mongo
    ports:
      # 2g. specify port mapping for the database
      - "27017:27017"
Listing 4: The docker-compose.yml file
The above listing has the following specifications (Note: Following numbers matches with the comments applied on the above code)
  1. Specify the docker-compose version. Please read about it from this link.
  2. Define the services block. This is used to define services that makes up our application - in our case its express application and mongo. These services can run in an isolated environment.
    2a. This is the name of the service, here we are giving the service name as express
    2b. Here we specify the folder name where we have the dockerfile so that when we run the docker-compose.yml file, it will fetch the required dependencies.
    2c. This step specifies the port mapping from the image with the local machine
    2d. Here we are defining the dependency our express application  on mongodb. The links specifies that the express service has dependency on db service.
    2e. Here we are defining the service settings for db service.
    2f. Here we are specifying that the mongo image must be pulled from the repository.
    2g. Here we are defining the port mapping for the MongoDB database.
Step 6: Run the following command to execute docker-compose.yml. This will build images and will make them ready to access:
 

docker compose up

This command will pull the Mongo from the docker hub repository as shown in Figure 5.
  
Figure 5: Pulling the Mongo image

The command will also build an image for node and express application as shown in the image 3. Once the build is successfully executed, the MongoDB Database and Express application will be started as shown in Figure 6.
  
Figure 6: Output of the docker-compose.yml file

The above image shows that the db instance for MongoDB is started and the express application also starts. Since the ports 4007 and 27017 are mapped with the local machine, the express API can be accessed from the following link:
http://localhost4007/api/products

Enter this link in the browser, it will show the result as following
{"status":200,"data":[]}

Open the Postman or Fiddler and perform the POST request as shown in Figure 7.
  
Figure 7: The POST request using Postman.
This will post the Product data to the API and will be saved in the MongoDB database. Now enter the address http://localhost:4007/api/products in the browser, it will show the result as shown in Figure 8.
  
Figure 8: The final result

The list of images created on the local machine can be seen using following command

  
  Figure 9: List of images

Conclusion:


Dockerization of an application makes it easy to share the application across different platforms without worrying about the application dependencies on the target production machine.





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

No comments: