Deploying RESTful APIs using Node.js, Express 4 to Kubernetes clusters

In this tutorial, we will go over how to build RESTful APIs using the Node.js Express framework, test them locally using docker-compose. We will then proceed to deploy this application to the Kubernetes.

1. Introduction

Express is a backend development framework built on top of Node.js, it enables the implementation of the client-server architecture. With its flexibility, it allows for the customization of the API endpoints, consequently, fitting our needs.

2. Prerequisites

To follow along with this tutorial, you need the following:

  • Node.js downloaded and installed in your local development environment.
  • Basic knowledge in Node.js’ Express framework.
  • RESTful APIs design.
  • Basic knowledge in Docker
  • Basic knowledge in kubernetes

3. Objectives

By the end of this article, you should be able to create a complete dynamic Express application and deploy it to the cloud using Docker.

4. Node.js application setup

Let’s start by importing required modules and create a running server:

//this node application is located in the index.js file
const http = require("http");

http.createServer(function (req, res) {
  
   res.writeHead(200, {'Content-Type': 'text/plain'});
 
   res.end('Hello World\n');
   
}).listen(8000);

console.log('Server started at http://127.0.0.1:8000/');

Now execute this application by running the command on the command line:

node index.js

Execution output:

The server started at http://127.0.0.1:8000/

5. Express packages setup

Add the following contents in your server.js script:

const express    = require('express');      
const app        = express();                
const bodyParser = require('body-parser');
// import the student schema defined in the student.js file
const  Student = require('./models/student'); 
//register router middleware
const router = express.Router();  


app.use(bodyParser.urlencoded({ extended: true }));  
app.use(bodyParser.json());

const port = process.env.PORT || 8000;        

const config = require('./config');

const mongoose = require('mongoose');

mongoose.connect(config.db[app.settings.env]); 

In the script above, we imported the Express package. Additionally, we imported packages that will aid in running our Express application and setting up a connection to the database.

Now that we have got a connection to the MongoDB database server, let’s define the model that we will use to get the list of students from a school database.

const mongoose     = require('mongoose');  
const Schema       = mongoose.Schema;

const StudentSchema   = new Schema({  
    student_id: String,
    name: String,
    registration_number: String,
    course: String,
    year_of_study: Number,
},
{
    timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
});

module.exports = mongoose.model('Student', StudentSchema);  

In the model above, we set up the student details we will be getting via our API.

6. RESTful APIs implementation

Now that we’ve set up our model and server file, in this section, let’s implement our RESTful APIs and deploy our application to the cloud.

router.get('/students/:student_id', function(request, response) {  
    Student.findOne({student_id: request.params.student_id}, function(err, Student) {

        if (err) 
        {
        response.status(500);

        response.setHeader('Content-Type', 'application/vnd.error+json');
        response.json({ message: "An error occurred, unable to get student details"});

    } 
    else if (Student == null) 
    {

        response.status(404);
        response.setHeader('Content-Type', 'application/vnd.error+json');
        response.json({ message: "ProductQuantity not found for product_id "+request.params.student_id});

    } 
    else 
    {

        response.status(200);
        response.setHeader('Content-Type', 'application/hal+json');

        let student_resource = halson({
        student_id: Student.student_id,
        name: Student.name,
        course: Student.course,
        year: Student.year_of_study,
        registration_number: Student.registration_number,
        created_at: Student.created_at
        }).addLink('self', '/students/'+Student.student_id)
        //response
        response.send(JSON.stringify(student_resource));

    }
    });    
});

// let's now register our routes
app.use('/', router);

// now start the server on port 8000

app.listen(port);  
console.log('Starting server on port ' + port);  

7. Dockerizing the Express application

Now that we’ve defined our core application API logics, let’s proceed to our main aim of the tutorial, dockerizing your RESTful Node.js Express application.

This section assumes you have Docker up and running in your Ubuntu machine.

Let’s proceed and define the contents of the Dockerfile to direct docker on how to build a container image of our Express application.

# the base image from which the app is built upon
FROM node: latest 
# Runs the mkdire command to create /usr/src/app inside docker container
RUN mkdir -p /usr/src/app  
# Sets the work directory to /usr/src/app 
WORKDIR /usr/src/app  
# Copies the contents of the current directory into the working directory inside the # docker container
COPY . /usr/src/app
# Exposes port 8000 outside the docker container
EXPOSE 8000  
# Runs the npm install command to install dependencies
RUN npm install  
# Provides the command required to run the application
CMD ["npm", "start"] 

This Dockerfile uses npm to install modules in our RESTful application. Let’s now proceed and set up the docker-compose configuration file that we’ll use to launch the Node.js Express application (including the MongoDB instance).

-------------------------
# Service name
student:  
# build in the current directory
  build: .
  # command to run the app
  command: npm start
  # Maps port 8000 inside docker container to port 8000 outside docker container
  ports:
  - "8000:8000"
  # linking the student to mongodb container
  links:
  - mongodb
  # env variables
  environment:
    - NODE_ENV=production
    - MONGODB_ADDRESS=mongodb
# mongodb service
mongodb:  
  # pulling mongodb image
  image: mongo

8. Setup YAML service to deploy Dockerized Node.js Express application

Now that we’ve dockerized our application locally, the next step involves deploying the application to the cloud.

Let’s proceed and set up the service to deploy the app as shown below

//service.yaml file
-----------------------------
services:

  inventory:
    git_url: git@github.com:myexample.git
    git_branch: main
    command: npm start
    build_root: .
    ports:
      - container: 8000
        http: 80
        https: 443
    env_vars:
      NODE_ENV: production

databases:  
  - mongodb

Note, make sure you change your git URL in the above service.

You can now log in to your favorite cloud vendor to deploy your dockerized application.

Now that we have a Docker container image, we need to create a deployment file. In the root directory, create a new file called deployment.yaml. This file will deploy the application to the Kubernetes engine.

Add the following snippets to the file:

apiVersion: v1
kind: Service
metadata:
  name: rest-test-service
spec:
  selector:
    app: rest-test-app
  ports:
  - protocol: "TCP"
    port: 3000
    targetPort: 8000
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rest-test-app
spec:
  selector:
    matchLabels:
      app: rest-test-app
  replicas: 5
  template:
    metadata:
      labels:
        app: rest-test-app
    spec:
      containers:
      - name: rest-test-app
        image: rest-test-app
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8000

The file has two parts:

  1. Service – The service acts as the load balancer. A load balancer is used to distribute requests to the various available servers.
  2. Deployment will act as the intended application. The user request hits the load balancer, then the load balancer distributes the request by creating the number of replicas defined in the deployment.yaml file. For example, in our case, we have five replicas for scalability, meaning that we will have 5 instances running at a time.

The benefit of multiple replicas is that if an instance crashes, the other application instances continue running.

The deployment.yaml file is connected to the Docker image created earlier, therefore to deploy the application to the Kubernetes cluster, we use the Docker image. The image will automatically create containers for the application when we deploy the application.

9. Deploying to Kubernetes service

We have dockerized our RESTful application, and now we need to deploy it to a Kubernetes engine.

Execute the command below in your terminal:

kubectl apply -f deployment.yaml

This command will deploy our service and application instances to the Kubernetes engine. After executing this command, we should be able to see that the rest-test-service and the rest-test-app are created successfully.

10. The deployment dashboard

Minikube and Kubernetes provide a dashboard to visualize the deployment. To see our deployment in that dashboard, execute the command below in your terminal.

minikube dashboard

We can see that our rest application was deployed and we can see the number of running instances. If a request is made, the load balancer distributes the number of hits the request had on the instances.

11. Accessing the application

We can access the application using the command below:

minikube start service: rest-test-service

12. Conclusion

In this tutorial, we’ve covered the key concepts of Node.js Express application RESTful APIs. We discussed how we can dockerize this application locally using Docker and deploy it to the Kubernetes.

Happy coding!