How to build a NodeJS cinema microservice and deploy it with docker
In this series, we’ll build a NodeJS microservice and deploy it using a Docker Swarm Cluster.
Here are the tools we’re going to use:
- NodeJS version 7.2.0
- MongoDB 3.4.1
- Docker for Mac 1.12.6
Before you attempt this guide, you should have:
- Basic knowledge in NodeJS
- Basic knowledge in Docker (and Docker installed)
- Basic knowledge in MongoDB (and the database service running. If you don’t, I suggest you follow my previous article How deploy a MongoDB replica set with Docker.)
But first, what is a microservice?
A microservice is a single self-contained unit which, together with many others, makes up a large application. By splitting your app into small units every part of it is independently deployable and scalable, can be written by different teams and in different programming languages and can be tested individually. — Max Stoiber
A microservice architecture means that your app is made up of lots of smaller, independent applications capable of running in their own memory space and scaling independently from each other across potentially many separate machines. — Eric Elliot
The benefits of microservices
- The application starts faster, which makes developers more productive, and speeds up deployments.
- Each service can be deployed independently of other services — easier to deploy new versions of services frequently
- Easier to scale development and can also have performance advantages.
- Eliminates any long-term commitment to a technology stack. When developing a new service you can pick a new technology stack.
- Microservices are typically better organized, since each microservice has a very specific job, and is not concerned with the jobs of other components.
- Decoupled services are also easier to recompose and reconfigure to serve the purposes of different apps (for example, serving both the web clients and public API).
The drawbacks of microservices
- Developers must deal with the additional complexity of creating a distributed system.
- Deployment complexity. In production, there is also the operational complexity of deploying and managing a system comprised of many different service types.
- As you’re building a new microservice architecture, you’re likely to discover lots of cross-cutting concerns that you did not anticipate at design time.
Architecture for your microservice
Let’s imagine that we are woking in the IT department of Cinépolis (A Mexican cinema), and they give us the task of restructuring their tickets and grocery store from a monolithic system to a microservice.
So for the first part of the “Build a NodeJS cinema microservice” series, we are going to focus only on the movie catalog service.
In this architecture we saw that we have 3 different devices who uses the microservice, the POS(point of sale), a mobile/tablet, and a computer, where the POS and the mobile/tablet has its own application developed (in electron) and consumes directly the microservice, and the computer accesses the microservice through web apps (web apps are considered by the gurus also like microservices 🤓).
Building the microservice
Ok so, let’s simulate that we are going to request a booking in our favorite cinema for a movie premiere.
First we want to see which movies are currently available in the cinema. The following diagram shows us how is going to be the inner communication with microservices through REST.
Our API for the movies service will have this raml specifications:
If you don’t know what RAML is you can check this great tutorial
The structure for the API projects will look like this:
- api/ # our apis
- config/ # config for the app
- mock/ # not necessary just for data examples
- repository/ # abstraction over our db
- server/ # server setup code
- package.json # dependencies
- index.js # main entrypoint of the app
Ok so let’s start. The first section to look at is the
repository. Here is where we do our query’s to the database.
As you may noticed, we provide a
db object and to the
collection object, the
db object is holding the database connection. Here we are abstracting the type of database we are connecting to, the repository object doesn’t know what kind of database is, in our case is a MongoDB connection, even though it doesn’t have to know if it’s a single database or a replica set connection, although that we are using mongodb syntax, we can abstract the repository functions even more by applying the Dependency Inversion principle from solid principles, from taking mongo syntax to another file and just call the interface of database actions (e.g. using mongoose models).
repository/repository.spec.js file for testing this module, i am going to talk about test later on the article, but if you want to check it, you can find it here at the github repo branch step-1.
The next file we are going to look at is the
Here what we are doing is, instantiating a new express app, verifying if we provide a repository and server port objects, then we apply some middleware to our express app, like
morgan for logging,
helmet for security, and a
error handling function, and at the end we are exporting a start function to be able to start the server 😎.
Helmet includes a whopping 11 packages that all work to block malicious parties from breaking or using an application to hurt its users.
If you want to hardener the microservice you can check this great article.
Ok now since our server is using our movieAPI, let’s continue checking the file
What we are doing here is creating the routes for our API, and calling our repo functions depending on the route listened, if you can see, our repo here is using an interface technique approach, here we are using the famously “coding for an interface not to an implementation”, since the express routes dosen’t know about if there’s a database object, database queries logic, etc, it only calls the repo functions that handles all of the database concerns.
Ok all of our files has unit tests adjacent to the source, let’s see how is our test for the
You can think of tests as safeguards for the applications you are building. They will run not just on your local machine, but also on the CI services so that failing builds won’t get pushed to production systems. — Trace by RisingStack
To write unit test, all the dependencies must be stubbed, meaning we are providing fake dependencies for a module. Let’s see how is our
As you can see we are stubbing the dependencies for the
movies API , and for the server we are verifying that we need to provided a server port and a repository object.
You can check all test files in the github repo of the article.
Let’s continue with how to create the
db connection object we passed to the repository module, now definition says that every microservice has have it’s own database, but for our example we are going to use a mongoDB replica set server, but each microservice will have it own database, if you don't now how to configure a mongoDB replset server, you can check this article for a deeper explanation.
How to deploy a MongoDB Replica Set using Docker
This article is going to be a walk-through in how to set up a MongoDB replica set with authentication using docker.
Here is the configuration we need to connect to a MongoDB database from NodeJS.
There’s probably a lot of better ways to do this, but basically we can create a connection to a mongoDB with replica set like this.
As you can see, we are passing a
options object, that has all the parameters that the mongo connection needs, and also we are passing an
event — mediator object that will emit the the
db object when we pass the authentication process.
Note* here i am using an event-emitter object because, with a promise approach for some reason it didn’t return the db object once it pass the authentication, the sequence gets idle. — so this could be a good challenge to see what’s happening and try to use a promise approach.
Now since we are passing a
options object for the parameters, let’s see from where this is coming from, so the next file to look at is the
Here is our config file, mostly all config codes are hard coded, but as you can see some attributes uses environment variables as an option. Environment variables are considered best practices, because this can hide database credentials, server parameters, etc.
And finally the last step for coding our API for the
movies-service is putting together everything with an
Here we are composing all the movies API service, we have a little error handling, then we are loading the configurations, starting the repository and finally starting the server.
So until now we have finished everything that concerns with a API development, you can check the repo at the step-1 branch.
If you go to the github repo of the article, you will see that there’s some commands for:
npm install # setup node dependencies
npm test # unit test with mocha
npm start # starts the service
npm run node-debug # run the server in debug mode
npm run chrome-debug # debug the node with chrome
npm run lint # lint the code with standard
And finally we got our first microservice up and running locally executing the
npm start command, but that is not what the title of the article said 🤔.
Now is time to put it in a Docker container as we mention it in the title of the article😁.
But first what we need is, have the Docker environment from the article for “creating a mongoDB replica set with docker”, and if you don’t have it you will have to do some additional modification steps to setup a database to our microservice, here some commands to be up to date just for testing purposes our movies-service.
So first let’s create our Dockerfile to dockerize our NodeJS microservice.
# Node v7 as the base image to support ES6
FROM node:7.2.0# Create a new user to our new container and avoid the root user
RUN useradd --user-group --create-home --shell /bin/false nupp && \
apt-get cleanENV HOME=/home/nuppCOPY package.json npm-shrinkwrap.json $HOME/app/COPY src/ $HOME/app/srcRUN chown -R nupp:nupp $HOME/* /usr/local/WORKDIR $HOME/app
RUN npm cache clean && \
npm install --silent --progress=false --productionRUN chown -R nupp:nupp $HOME/*
USER nuppEXPOSE 3000CMD ["npm", "start"]
We are taking the NodeJS image as the base for our docker image, then we create a user for avoiding non-root user, then we copy the src to our image then we install the dependencies, we expose a number port and finally we instantiate our movies-service.
Next we have to build our docker image, with the following command:
$ docker build -t movies-service .
Let’s look at the build command first.
docker buildtell the engine we want to create a new image.
-t movies-servicetag this image with the tag
movies-service. We can refer to this image by tag from now on.
.use the current directory to find the
After some console output we have our new image with our NodeJS app, so now what we need to do is to run our image with the following command:
$ docker run --name movie-service -p 3000:3000 -e DB_SERVERS="192.168.99.100:27017 192.168.99.101:27017 192.168.99.100:27017" -d movies-service
In the command above we are passing an env variable which is an array of servers that needs to connect to the mongoDB replset, this is just for ilustration, there are better ways to do this, like reading an env file for example.
Now that we have our container up and running let’s retrieve our
docker-machine ip machine-name to have the ip of our microservice, and we are ready to make an integration test to our microservice, another option for testing could be JMeter, is a good tool for simulating http requests, and here is a great tutorial of JMeter.
This is our
integration-test that will check an API call :D.
Time for a recap
What we have done…
We’ve made only the first part of this communication flow, we made the movies service for consulting the movie premiers available in the cinema, we built the movies services API in NodeJS, first we design the api with a RAML specification, then we start building our API, and made the corresponding unit test, finally we compose everything to have our microservice complete, and be able to start our movies service server.
Then we put our microservice into a Docker container, to be able to make some integration test.
We’ve seen a lot of development in NodeJS, but there’s a lot more that we can do and learn, this is just a sneak peak. I hope this has shown some of the interesting and useful things that you can use for Docker and NodeJS in your workflow.
Let me remember you, this article is part of “Build a NodeJS cinema microservice and deploying it with docker” series.
I will put below the link of the continuation, the part 2 of this series.
The complete code is on Github
You can check the complete code of the article at the following link.
To get better with NodeJS you can check this sites
- 10 Tips to Become a Better Node Developer in 2017
- NodeJS tutorial series — Node Hero (covers almost all node topics)
- NodeJS Security Checklist
- NodeJS at Scale
I hope you enjoyed this article. I’m still exploring the NodeJS and microservices world, so I’m open to accept feedback and contributions.
If you enjoyed this article, recommend it to a friend.
You can also follow me on Twitter.
Until next time 😁👨🏼🎨👨🏻💻