In this post we shall develop a Node Express app from scratch and then containerize it using Docker. Before we start, you will need to ready your environment with the following pre-requisites for your specific operating system:
- Install Node.js from https://nodejs.org/en/download/
- Install MongoDB from https://www.mongodb.com/download-center#community
- Install Docker from https://docs.docker.com/install/
- Optionally, you can install Visual Studio Code from https://code.visualstudio.com/Download
In a terminal window, go to a suitable location on your computer's hard drive and create a directory named nodemongo:
mkdir nodemongo
Change into the nodemongo directory:
cd nodemongo
Let us initialize a node application. This is done by running the following command:
npm init
Respond as below:
package name: [Hit ENTER]
version: [Hit Enter]
description: Dockerizing node/express/mongo app
entry point: server.js
test command: [Hit Enter]
git repository: [Hit Enter]
keywords: [Hit Enter]
author: [Enter your name]
license: [Hit Enter]
The package.json file is displayed and you are asked to confirm. Enter y to confirm.
This is what my project.json file looks like:
{
"name": "nodemongo",
"version": "1.0.0",
"description": "Dockerizing node/express/mongo app.",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Medhat Elmasry",
"license": "ISC"
}
We will need two node packages: express and mongojs. Install these packages with the following command:
npm install express mongojs
I am using Visual Studio Code to edit my application. Use whatever editor you are familiar with like sublime, atom, brackets, etc....
Create a file named server.js in the nodemongo folder and add to it the following code:
var express = require("express");
var users = require("./routes/");
var config = require('./config');
var app = express();
app.use("/", users);
app.listen(config.port, function() {
console.log("Server started on port " + config.port)
});
Lines 2 and 3 above suggest that we are missing folders /routes and /config. Therefore, create folders routes and config inside the nodemongo directory.
Inside the /config directory, create a JavaScript file named index.js and add to it the following code:
module.exports = {
// Setting port for server
'port': process.env.PORT || 3000,
// Setting mongo host name
'mongo_host': process.env.MONGO_HOST || 'localhost',
// Setting mongo port number
'mongo_port': process.env.MONGO_PORT || 27017,
// Setting mongo database name
'mongo_database': process.env.MONGO_DATABASE_NAME || 'demodb',
};
The above code configures environment variables for: (1) the server listening port number, (2) the MongoDB host name, (3) the MongoDB listening port number and (4) the MongoDB database name.
Inside the /routes directory, create another JavaScript file named index.js and add to it the following code:
var express = require("express");
var mongojs = require('mongojs');
var config = require('../config');
var connStr = 'mongodb://' + config.mongo_host;
connStr += ':' + config.mongo_port;
connStr += '/' + config.mongo_database;
var db = mongojs(connStr, ['students']);
var usersCollection = db.collection('users');
var router = express.Router();
router.get('/', (req, res, next) => {
usersCollection.count({},(err, docCount) => {
if (docCount == 0) {
const users = [
{"name":"user_1","email":"user_1@bogus.com"},
{"name":"user_2","email":"user_2@bogus.com"},
{"name":"user_3","email":"user_3@bogus.com"}
];
// use the Event model to insert/save
usersCollection.save(users, (err, data) => {
if (err) {
res.send(err);
}
})
}
})
usersCollection.find( (err, data) => {
if (err)
res.send(err);
res.json(data);
})
});
router.get('/add', (req, res, next) => {
var name = 'user_'+ Math.floor(Math.random() * 1000);
var email = name + '@bogus.com';
var doc = {'name': name, 'email': email};
usersCollection.save(doc, (err, data) => {
if (err) {
res.send(err);
}
res.json(data);
})
});
// get single user
router.get("/users/:id", (req, res, next) => {
usersCollection.findOne({_id: mongojs.ObjectId(req.params.id)},function(err, data){
if (err) {
res.send(err);
}
res.json(data);
});
});
module.exports = router;
The above code sets up the following API endpoints:
http://localhost:3000/ | Displays all documents in the users collection. If the users collection is empty then it will be seeded with three documents. |
http://localhost:3000/add | Adds a user document with a randomly generated name and email |
http://localhost:3000/users/:id | Displays a singe document by MongoDB object id |
If the MongoDB database server has not started already, start it. At this point, we are able to test our application and make sure it is working as expected.
After starting the database, launch the application by executing the following terminal command:
You should see the following message in the terminal window:
> nodemongo@1.0.0 start D:\demo\_docker\nodemongo
> node server.js
Server started on port 3000
This indicates that our node web server is running and listening on port number 3000. Point your browser to http://localhost:3000/. You should see the following seeded data comprising of three JSON objects in your browser:
[{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"},{"_id":"5a9b8f225e78082ddccba362","name":"user_2","email":"user_2@bogus.com"},{"_id":"5a9b8f225e78082ddccba363","name":"user_3","email":"user_3@bogus.com"}]
Try adding a user by pointing to http://localhost:3000/add. It should randomly generate a user document similar to the following:
{"name":"user_20","email":"user_20@bogus.com","_id":"5a9b8fdd5e78082ddccba364"}
If you try http://localhost:3000/ again, you should see four JSON objects similar to the following:
[{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"},{"_id":"5a9b8f225e78082ddccba362","name":"user_2","email":"user_2@bogus.com"},{"_id":"5a9b8f225e78082ddccba363","name":"user_3","email":"user_3@bogus.com"},{"_id":"5a9b8fdd5e78082ddccba364","name":"user_20","email":"user_20@bogus.com"}]
Lastly, you can get a document by MongoDB object ID with endpoint http://localhost:3000/users/:id. Copy one of the IDs and and it to the address line. I used one of my IDs and pointed my browser to the following endpoint: http://localhost:3000/users/5a9b8f225e78082ddccba361 and experienced the following response:
{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"}
Of course, you will not have the same ID as I do and should use one of the IDs in your users documents.
At this stage, we have determined that our application works as expected. The next and final step is to containerize both MongoDB and our Node/Express API application.
Stop both the Node web server and the MongoDB database server.
Create a text file named .dockerignore in the root of your node project and add to it the following content. This instructs Docker not to copy the contents of node_modules into the container:
Create a text file named DockerFile in the root folder of your application and add to it the following:
FROM node:9.7.1
WORKDIR /app
COPY package.json /app/package.json
RUN npm install
COPY . /app
EXPOSE 3000
Above are instructions to create a Docker image that will contain our Node/Express API web application. I describe each line below:
FROM node:9.7.1 Base image node:9.7.1 will be used
WORKDIR /app The working directory in the container is /app
COPY package.json /app/package.json package.json is copied to /app in the container
RUN npm install This is run inside the containers /app directory
EXPOSE 3000 Port 3000 will be exposed in the container
Create another text file named docker-compose.yml in the root of your node project, and add to it the following content:After starting the database, launch the application by executing the following terminal command:
npm start
You should see the following message in the terminal window:
> nodemongo@1.0.0 start D:\demo\_docker\nodemongo
> node server.js
Server started on port 3000
This indicates that our node web server is running and listening on port number 3000. Point your browser to http://localhost:3000/. You should see the following seeded data comprising of three JSON objects in your browser:
[{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"},{"_id":"5a9b8f225e78082ddccba362","name":"user_2","email":"user_2@bogus.com"},{"_id":"5a9b8f225e78082ddccba363","name":"user_3","email":"user_3@bogus.com"}]
Try adding a user by pointing to http://localhost:3000/add. It should randomly generate a user document similar to the following:
{"name":"user_20","email":"user_20@bogus.com","_id":"5a9b8fdd5e78082ddccba364"}
If you try http://localhost:3000/ again, you should see four JSON objects similar to the following:
[{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"},{"_id":"5a9b8f225e78082ddccba362","name":"user_2","email":"user_2@bogus.com"},{"_id":"5a9b8f225e78082ddccba363","name":"user_3","email":"user_3@bogus.com"},{"_id":"5a9b8fdd5e78082ddccba364","name":"user_20","email":"user_20@bogus.com"}]
Lastly, you can get a document by MongoDB object ID with endpoint http://localhost:3000/users/:id. Copy one of the IDs and and it to the address line. I used one of my IDs and pointed my browser to the following endpoint: http://localhost:3000/users/5a9b8f225e78082ddccba361 and experienced the following response:
{"_id":"5a9b8f225e78082ddccba361","name":"user_1","email":"user_1@bogus.com"}
Of course, you will not have the same ID as I do and should use one of the IDs in your users documents.
At this stage, we have determined that our application works as expected. The next and final step is to containerize both MongoDB and our Node/Express API application.
Stop both the Node web server and the MongoDB database server.
Create a text file named .dockerignore in the root of your node project and add to it the following content. This instructs Docker not to copy the contents of node_modules into the container:
node_modules
Create a text file named DockerFile in the root folder of your application and add to it the following:
FROM node:9.7.1
WORKDIR /app
COPY package.json /app/package.json
RUN npm install
COPY . /app
EXPOSE 3000
Above are instructions to create a Docker image that will contain our Node/Express API web application. I describe each line below:
FROM node:9.7.1 Base image node:9.7.1 will be used
WORKDIR /app The working directory in the container is /app
COPY package.json /app/package.json package.json is copied to /app in the container
RUN npm install This is run inside the containers /app directory
EXPOSE 3000 Port 3000 will be exposed in the container
version: "3"
services:
db:
image: mongo:3.6.3
ports:
- "27017:27017"
restart: always
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
links:
- db
environment:
- MONGO_HOST=db
- MONGO_PORT=27017
- MONGO_DATABASE_NAME=demodb
command: node /app/server.js
Below is an explanation of what this file does.
We will be having two containers. Each container is considered to be a service. The first service is named db and will host MongoDB. The second service is named web and will host our Node/Express API app.
The most current version of docker-compose is version 3. This is the first line in our docker-compose.yml file.
The MongoDB Container
Image mongo version 3.6.3 will be used for the MongoDB container.
restart: always is so that if the container stops, it will be automatically restarted.
Port 27017 is the default MongoDB listening port. This port number, inside the container, is being mapped to the same port number outside the container.
Node/Express API app Container
The container will be built using the instructions in the Dockerfile file and the context used is the current directory.
Port 3000 in the web container is mapped to port 3000 on the host computer.
The environment variables needed by the web app are:
- MONGO_HOST pointing to the MongoDB service name
- MONGO_PORT sets the MongoDB listening port number
- MONGO_DATABASE_NAME is the name which we want to use for the database
Running the yml file
To find out if this all works, go to a terminal window and run the following command:
docker-compose -f docker-compose.yml up
Point your browser to http://localhost:3000/ and you should see three seeded users JSON objects as below:
docker ps -a
You will see something similar to this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
979d458c9ba3 nodemongo_web "node /app/server.js" 21 minutes ago Up 20 minutes 0.0.0.0:3000->3000/tcp nodemongo_web_1
ae80a1b9283c mongo "docker-entrypoint.s…" 21 minutes ago Up 21 minutes 0.0.0.0:27017->27017/tcp nodemongo_db_1
docker-compose -f docker-compose.yml down
Thanks for coming this far in my tutorial and I hope you found it useful.
No comments:
Post a Comment