Building Nodejs Microservice - A Cloud-Native Approach - Part 1

If you're reading this article, i assume that you know how to build an Application using Nodejs. few may know how to build frontend as well.

Well, i have a task for you before getting into this article. Build me a Project Management Application using Nodejs.

demo

if you implement something like this,

Monolith Architecture

Then, you are not probably in 2021. Don't get me wrong. there is nothing wrong with above approach. i still works well for lot of application requirements.

But, let me tell you how it's going to make difference in application development. Let's say that you implement your project using Monolith Architecture and your application starts to evolve, continous iteration and building features on top of it.

At some point of it, you will start to feel uncomfortable and your code become unmaintainable. it makes the application overly complicated and other developers fears to build features or maintain it.

Here are the side effects of it,

  • Application gets overly complicated and hard to maintain
  • Adaptation for new technology will not be an option.
  • Implementing a new feature become a time consuming process etc.

That's when the Microservices comes into picture,

gif

Let's implement the sample application architecture in microservice way,

Microservices Architecture style

Now, the application modules are decomposed into isolates microservices. each services will be independent with it's own code and dependancy.

So far, we have seen the difference between Monolith and Microservices. If you watch it carefully, i introduced an another term called Cloud Native Approach .

Well, let's see what does it mean by that,

What is Cloud Native Application?

Cloud Native is a modern way to build a Large scale system. it is a systematic approach to build a large system which can change rapidly with zero downtime and resilient at the same time.

There are lot of open source applications evolved over the time to make it possible. tools such as Docker, Kubernetes, Helm , Prometheus and gRPC helps us to build such applications.

If somebody asks you, what does it mean by Cloud Native Application Development ?. just tell them that,

Cloud native application development is an approach to building and deploying applications quickly, while improving the quality and reducing risk.it is just a way to build scalable, resilient and fault-tolerant applications with zero down-time in deployment.

To learn more about Cloud-native Application, check this awesome docs from Microsoft

Cloud based approach follow 12 factor methodology which describes set of principle and best practices that we need to follow while building an cloud-native application.

Here we are going to implement two simple micro-services which are project and task services. it will be a simple Express Application.

Main purpose of this article is to make you understand how to build a Microservices in cloud native approach. we will not be focusing on business logic of each microservices.

Once you understand the outer layer. you can easily build your own business logic inside each services.

Note: I assume that you have basic knowledge on Docker and Kubernetes. if you are new to those topics. i suggest you to check these articles

Enough of the theory. let's implement a Nodejs Microservices and see it in action

gif

Project Service

Create a simple Express Application with the basic boilerplate code.

1const express = require("express")
2const bodyParser = require("body-parser")
3const app = express()
4
5app.use(bodyParser.json())
6app.use(bodyParser.urlencoded({ extended: false }))
7
8app.get("/project", (req, res) => {
9 res.send("Welcome to ProjectService")
10})
11
12app.listen(4500, () => {
13 console.log("Listening on PORT 4500")
14})

Now, let's dockerize our Nodejs Application. create a Dockerfile inside your project service directory.

1FROM node:alpine
2
3WORKDIR /usr/app
4
5COPY package*.json ./
6
7RUN npm install
8
9COPY . .
10
11EXPOSE 4500
12
13CMD ["node","app.js"]

Don't forgot to add .dockerignore inside your root directory

1node_modules

Task Service

Do the same for Task service,

  1. Create a Simple Express Application
  2. Dockerize your task service
  3. add .dockerginore inside your task service
1const express = require("express")
2const bodyParser = require("body-parser")
3const app = express()
4
5app.use(bodyParser.json())
6app.use(bodyParser.urlencoded({ extended: false }))
7
8app.get("/task", (req, res) => {
9 res.send("Welcome to TaskService")
10})
11
12app.listen(4501, () => {
13 console.log("Listening on PORT 4501")
14})
1FROM node:alpine
2
3WORKDIR /usr/app
4
5COPY package*.json ./
6
7RUN npm install
8
9COPY . .
10
11EXPOSE 4500
12
13CMD ["node","app.js"]
1node_modules

Once you complete the docker configuration. you can build docker images using the command,

Nodejs Docker Images Microservices

Nodejs Microservices

Now, we have our containerized microservices. if you run the docker container, each containers will be running in a separate ports.

Nodejs Docker Containers

But wait, we want our application to run in a single port. right?

Nodejs Docker Containers

To achieve this, we need kubernetes. Kubernetes is a orchestration tool that helps us to manage our docker containers and load balance between them etc.

Infrastructure

Here, we want to route our the request to appropriate docker containers. let's implement kubernetes on top of our Nodejs Microservices.

Note: If you're new to Kubernetes, i recommend you to check this article to understand the basics of kubernetes

To explain it simpler, Kubernetes requires Service and Deployment to manage the pods inside Nodes.

Let's create Service and Deployment config for each micro-services,

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: project-depl
5spec:
6 selector:
7 matchLabels:
8 app: projects
9 template:
10 metadata:
11 labels:
12 app: projects
13 spec:
14 containers:
15 - name: projects
16 image: ganeshmani009/projectservice
17 resources:
18 limits:
19 memory: "128Mi"
20 cpu: "500m"
21 ports:
22 - containerPort: 4500
23---
24apiVersion: v1
25kind: Service
26metadata:
27 name: project-srv-clusterip
28spec:
29 selector:
30 app: projects
31 ports:
32 - name: projects
33 protocol: TCP
34 port: 4500
35 targetPort: 4500

Let's break down Deoployment config to understand it better,

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: project-depl
5spec:
6 selector:
7 matchLabels:
8 app: projects
9 template:
10 metadata:
11 labels:
12 app: projects
13 spec:
14 containers:
15 - name: projects
16 image: ganeshmani009/projectservice
17 resources:
18 limits:
19 memory: "128Mi"
20 cpu: "500m"
21 ports:
22 - containerPort: 4500
1metadata:
2 name: project-depl

metadata name specifies the name of deployment.

1matchLabels:
2 app: projects

matchLabels creates Pod with the specified name here. After that we create Pod with template

1template:
2 metadata:
3 labels:
4 app: projects
5 spec:
6 containers:
7 - name: projects
8 image: ganeshmani009/projectservice
9 resources:
10 limits:
11 memory: "128Mi"
12 cpu: "500m"
13 ports:
14 - containerPort: 4500

Kubernetes Services

1apiVersion: v1
2kind: Service
3metadata:
4 name: project-srv-clusterip
5spec:
6 selector:
7 app: projects
8 ports:
9 - name: projects
10 protocol: TCP
11 port: 4500
12 targetPort: 4500

Here, we specify the kind as Service, then metadata name as project-srv-clusterip.

selector specifies which pod that needs to be mapped with Service. here, it's mentioned as projects.

port specifies the incoming port on the request. targetPort specifies the port to which the request needs to be forwarded to.

Task Service Infrastructure

1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: task-depl
5spec:
6 selector:
7 matchLabels:
8 app: tasks
9 template:
10 metadata:
11 labels:
12 app: tasks
13 spec:
14 containers:
15 - name: tasks
16 image: ganeshmani009/taskservice
17 resources:
18 limits:
19 memory: "128Mi"
20 cpu: "500m"
21 ports:
22 - containerPort: 4501
23---
24apiVersion: v1
25kind: Service
26metadata:
27 name: task-clusterip-srv
28spec:
29 selector:
30 app: tasks
31 ports:
32 - name: tasks
33 protocol: TCP
34 port: 4501
35 targetPort: 4501

To run the kubernetes cluster, we can use the command kubectl apply -f <file name>

Kubernetes Nodejs Microservices

Kubernetes Nodejs Microservices

One final thing that we need to set in kubernetes configuration is a controller to handle the request from outside world to kubernetes clusters. because, we can't access the kuberetes clusters directly.

To do that, we need ingress control to access the clusters. let's set ingress controller,

create ingress-srv.yml inside the infra directory and add the following config,

1apiVersion: networking.k8s.io/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-srv
5 annotations:
6 kubernetes.io/ingress.class: nginx
7spec:
8 rules:
9 - host: dialogapp.co
10 http:
11 paths:
12 - path: /project
13 backend:
14 serviceName: project-srv-clusterip
15 servicePort: 4500
16 - path: /task
17 backend:
18 serviceName: task-clusterip-srv
19 servicePort: 4501

Here, we use nginx as ingress controller. there are other controller that you can use in our kubernetes applications.

1apiVersion: networking.k8s.io/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-srv
5 annotations:
6 kubernetes.io/ingress.class: nginx

On the above code, we specify apiVersion and king of configuration. then, we set some meta-data. important thing to note here is the annotation which is nginx here. if we want any other controller, our configuration will change based on that.

1spec:
2 rules:
3 - host: dialogapp.co
4 http:
5 paths:
6 - path: /project
7 backend:
8 serviceName: project-srv-clusterip
9 servicePort: 4500
10 - path: /task
11 backend:
12 serviceName: task-clusterip-srv
13 servicePort: 4501

After that, we have rules which specifies host and http paths mainly. host is your application DNS. here, we specify our local server domain set in [hosts](https://setapp.com/how-to/edit-mac-hosts-file) config file.

Two important things to note here is,

  1. path : it specifies the path that we want to access our particular microservices. let's say that we want to access our Project Microservice in route /project-api we need to specify it in the path.
  2. backend : it specifies the kubernetes cluster that we want to access in the specified route.

Now, to run the ingress controller. we need to use the following command,

Kubernetes Ingress Controller

Finally, That complete our Microservice Configuration and setup.

gif

Let's access our microservice application routes in the browser and see whether it works or not,

App Result

App Result

It works as we expected. Now, we can build many microservices by following the same process and implement our business logics inside the microservices.

complete source can be found here

Cloud Native Web Development

A Hands-On Guidebook to Learn Cloud Native Web Development - From Zero To Production

To Read More

Modern React Redux Toolkit - Login ...

User Authentication is one of the common workflow in web applications. In this t...

Building Nodejs Microservice - A Cl...

This Article explains everything about how to build Nodejs Microservices in clou...

I Accidentally wiped the entire dat...

One of the tragic accident in my job turned out to be good learning for me in re...

Never miss a story from us, subscribe to our newsletter