Building a Load Balancer with Node.js and Express Part — 1

Suneel Kumar
3 min readSep 11, 2023
Photo by Marcin Simonides on Unsplash

Load balancing is an important technique for distributing incoming requests across multiple backend servers. This helps ensure high availability and scalability of web applications.

In this article, we will build a simple load balancer in Node.js using the Express framework.

Overview

Our load balancer will have the following features:

  • Accept incoming HTTP requests on port 80
  • Forward requests in a round-robin fashion to two backend servers on ports 8081 and 8082
  • Health check endpoints on backend servers for monitoring
  • Automatically remove unhealthy backends from the pool
  • Basic logging of requests

The load balancer will run as a Node.js module and balance requests between simple Express apps running on different ports to simulate backend servers.

Backend Servers

First, let’s setup two simple Express servers that will act as our backend application servers to handle requests from the load balancer.

Create a server.js file:

const express = require('express');

const app = express();

app.get('/', (req, res) => {
res.send('Hello from server!');
});

app.listen(8081, () => {
console.log('Backend server running on port 8081');
});

Copy this file and rename it to server2.js, changing the port to 8082. These will be our two backend servers.

Load Balancer

Now let’s build the main load balancer module.

Create a loadBalancer.js file:

const express = require('express');
const axios = require('axios');

const app = express();

// Backend servers
const servers = [
'http://localhost:8081',
'http://localhost:8082'
];

// Current index of backend server
let currentIndex = 0;

// Function for getting next backend server
function getNextServer() {
currentIndex++;
if (currentIndex >= servers.length) {
currentIndex = 0;
}

return servers[currentIndex];
}

// Health check
async function healthCheck() {

// Loop through servers and health check each one
for (let i = 0; i < servers.length; i++) {

const result = await axios.get(servers[i] + '/health');

// If unhealthy, remove from servers list
if (result.status !== 200) {
servers.splice(i, 1);
i--;
}
}

// Add servers back once they become available
setInterval(async () => {
let serverAdded = false;
for (let i = 0; i < servers.length; i++) {
const result = await axios.get(servers[i] + '/health');
if (result.status === 200 && !servers.includes(servers[i])) {
servers.push(servers[i]);
serverAdded = true;
}
}

if (serverAdded) {
console.log('Server added back to pool');
}
}, 5000);

}

// Run health check
healthCheck();

// Log requests
app.use((req, res, next) => {
console.log(`${req.method} request to ${req.url}`);
next();
});

// Handler for incoming requests
app.get('*', async (req, res) => {

// Get next backend server
const server = getNextServer();

// Forward request
try {
const result = await axios.get(server + req.url);
res.status(result.status).send(result.data);
} catch (err) {
res.status(500).send('Failed to connect to backend');
}
});

app.listen(80, () => {
console.log('Load balancer running on port 80');
});

This implements the load balancer logic we outlined earlier. It keeps a list of backend servers and loops through them in a round-robin fashion to forward requests. It also runs periodic health checks and removes unhealthy servers automatically.

To start the load balancer, run:

node loadBalancer.js

In addition, start the two backend servers:

node server.js
node server2.js

You should see the logging from the load balancer that it is running on port 80.

Now when you send requests to http://localhost, they will be distributed between the two backend servers in a round robin fashion.

If you take down one of the backend servers, the load balancer will detect it as unhealthy and remove it from the pool automatically.

In a real production scenario you would want to add features like caching, content switching, SSL termination etc. But this demonstrates the core concepts for implementing a load balancer in Node.js with health checks and round robin distribution between backends.

--

--