Load balancing your NodeJS app with NGINX and PM2
NodeJS is a javascript runtime based on the Chrome V8 engine that lets you run javascript code outside a web browser. NodeJS is scalable since the event-loop is single-threaded. The NodeJs process consumes lesser memory and processing power than other server-side languages. NodeJS being single-threaded also comes with some of its cons. This is the main reason why NodeJS is not advisable to use for CPU-intensive tasks. There are some workarounds to maximize your server resources.
A load balancer acts as the “traffic cop” sitting in front of your servers and routing client requests across all servers capable of fulfilling those requests in a manner that maximizes speed and capacity utilization and ensures that no one server is overworked, which could degrade performance. If a single server goes down, the load balancer redirects traffic to the remaining online servers. When a new server is added to the server group, the load balancer automatically starts to send requests to it.
Load balancing with PM2
PM2 is a daemon process manager that will help you manage and keep your application online. It also includes an automatic load balancer that will share all HTTP[s]/Websocket/TCP/UDP connections between each spawned process using Node Cluster. Getting started with PM2 is straightforward, it is offered as a simple and intuitive CLI, installable via NPM.
Install pm2 globally
yarn global add pm2
On your package.json you can add a script to startup your NodeJs using PM2
"scripts": {
"start": "pm2 start src/app.js -i 0 --name nodeApp",
}
This command would start your application with the maximum processes depending on available CPUs. PM2 is the perfect manager for clusters on NodeJs. The packages take the work off your hands and provide a set of commands to maximize your resources and monitor all your NodeJs processes. You can consult the PM2 documentation for more information. https://pm2.keymetrics.io/docs/usage/quick-start/
Load balancing with NGINX and NodeJs
Nginx is a fast and lightweight alternative to the sometimes overbearing Apache 2. However, Nginx just like any kind of server or software must be tuned to help attain optimal performance. For this process you need to have docker installed on your local machine, Let’s get started.
Some reasons you should consider using NGINX with NodeJS
- Reverse proxy: It serves as a routing mechanism that helps you direct your traffic to the appropriate server. This comes in very handy in a micro-service architecture.
- Cache static files: It can be used to serve static files (HTML, CSS, JavaScript, images, fonts) directly to the browsers, letting the NodeJs web app concentrate on the dynamic parts.
- Implement SSL/TLS and HTTP/2: NGINX can be used as an SSL off-loader so that your NodeJS web app doesn’t have to decrypt the requests and encrypt the responses. You can easily issue an SSL certificate for your server with Let-encrypt on your NGINX server.
- Security: It can be used to prevent DDoS attacks, IP blacklisting and whitelisting, etc.
Installation and setup
Create a Dockerfile in your root directory.
FROM node:lts-alpineWORKDIR /appCOPY . .
COPY package.*json .
COPY nginx.conf /etc/nginx/nginx.confRUN yarn EXPOSE 3000CMD ["yarn", "start"]
We are using the Node LTS alpine image, copying the Nginx configuration from our root directory to the Nginx configuration directory in the container, and exposing port 3000.
Create a docker-compose.yml file
version: '3.5'services:
node_app:
container_name: nodeApp
build: .
ports:
- '3000:3000'
networks:
- node_connect
volumes:
- './node_modules:/app/node_modules'
- '.:/app'
restart: always node_server:
container_name: nodeServer
image: 'nginx:1.21.3-alpine'
ports:
- '3005:80'
networks:
- node_connect
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- node_appnetworks:
node_connect:
driver: bridge
We are basically trying to run multiple containers on Docker here, we have specified our Node app to run on port 3000 and it’s going to build using the Dockerfile we created earlier. For NGINX we are exposing port 3005, so we would be accessing our NodeJS app with the NGINX port. Both containers have a persisting volume and they run on the same Docker network.
Create an Nginx configuration file.
worker_processes 4;events { worker_connections 1024; }http { server {
listen 80;
charset utf-8; location / {
proxy_pass http://node_app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
node_app here refers to our NodeJS service defined on the docker-compose.yml file.
To serve your NodeJS application with NGINX using Docker run.
docker-compose up
You can also consider combining NGINX and PM2.
yarn add pm2
It’s is advisable to use pm2 in production, it is not recommended for development. You can stick with Nodemon for development. Here’s the link to a sample repository that uses NodeJS with NGINX and PM2. https://github.com/bencoderus/node-engine-x
Your application would be accessible on port 3005. If you found this article resourceful, a clap would actually go a long way 😉