Photo by Mike U / Unsplash

Deploying an application is one of the most prominent parts of the software development lifecycle. When working with Node.js, deploying our application can be challenging. Fortunately, some tools make it easier, like PM2 who keeps your Node.js application alive and gives the possibility to scale and distribute the load between the instances of the application.

Once the application is live, it is not accessible to the world, and you need a Webserver + a domain name to make it possible. In this tutorial, we will see how to deploy a Node.js application using PM2 and do a reverse proxy with Nginx.

Here is the picture to summarize what we want to achieve

Schema of the communication between the browser and the web application

Prerequisites

To follow this tutorial, you need to have these elements

  • A ready-to-use VPS; if you don't have one, I wrote a post on setting up a VPS. Β You can buy a VPS on DigitalOcean. Sign up with my referral link to get 100$ credits to use over 60 days.
  • A domain or subdomain name
  • An SSH client (I use Termius)
  • Node.js 12+ and Nginx installed on the VPS

Create a simple Node.js application

The first step is to create a sample app we will deploy on the server and make it accessible to the world. We will use the boilerplate we created in this tutorial.

git clone https://github.com/tericcabrel/node-ts-starter.git -b express deploy-node-app

cd deploy-node-app

yarn install

yarn start

Navigate to the URL http://localhost:4500/ to make sure it works as expected.

Update the file src/index.ts with the code below:

import express from 'express';
import dotenv from 'dotenv';

dotenv.config();

const HOST = process.env.HOST || 'http://localhost';
const PORT = parseInt(process.env.PORT || '4500');
let requestCount = 0;

const app = express();

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

app.get('/', (req, res) => {
  requestCount++;

  return res.json({
    message: 'Hello World!',
    numberOfCall: requestCount,
  });
});

app.listen(PORT, () => {
  console.log(`Application started on URL ${HOST}:${PORT} πŸŽ‰`);
});

When we hit the endpoint with this code, it will increment the number of calls and display them in the browser.

Running the Node.js application locally.

Now, let's transpile the Typescript code to JavaScript.

yarn tsc

A folder named build will be created with the transpiled code. Now we can run the application with Node.js:

node build/index.js

The code still works as expected now we want to deploy it on the VPS.

Package the project and copy it to the server

The build contains only the index.js, but if zip it and run on the server, it will not work because the dependencies don't exist, so we will add the package.json and the yarn.lock.

cp package.json yarn.lock build

tar czf backend.tar.gz build/.

The second line compresses the build folder to a file called backend.tar.gz.

The next step is to copy the file to the VPS in the $HOME folder; we will use SCP, which stands for Secure Copy. It allows performing the distant copy.

The syntax is:

scp -P $PORT $ZIPPED_FILENAME $USER@$HOST:./
  • $PORT: Replace this with the port of your VPS
  • $ZIPPED_FILENAME: The compressed file on the local computer we want to copy on the server. The value is backend.tar.gz
  • $USER: Replace the username created on your VPS
  • $HOST: Replace this with the IP address of your VPS.

An example of the whole expression is:

scp -P 4321 backend.tar.gz user@192.186.11.12
Copy the application source to the server

Run the application with PM2

The file is copied to the server in the home's directory of the user. Log into, uncompress the application and run with PM2.

ssh -P $PORT $USER@$HOST

tar xf backend.tar.gz

cd build

yarn install

pm2 start index.js --name backend-app

Below, all these steps in a picture.

Running the application with PM2.

As we see, our app is working as expected

To install PM2, just run the command below:

npm install -g pm2

With PM2, you can create a configuration file that will be used to launch your application. You can indicate the name of the application, the entry point, the environment variables to inject, and so on. Inside the build directory, create a file called ecosystem.config.js and add the code below:

module.exports = {
  apps: [
    {
      name: 'backend-app',
      script: './index.js',
      watch: false,
      force: true,
      env: {
        PORT: 4500,
        NODE_ENV: 'production',
        MY_ENV_VAR: 'MyVarValue',
      },
    },
  ],
};

Now to launch your app, you can do: pm2 start ; behind the scene, PM2 will detect the ecosystem.config.js, parse it and extract the required information to launch Β the application

Note: In the ecosystem.config.js, the property "apps" take an array of applications as value, meaning you can manage many applications with a single file.

Set up a Reverse proxy with Nginx

The application is running on the server, but we can access it from the outside. We will create a subdomain then use Nginx to do a reverse proxy to the application.

My domain's name is tericcabrel.com; I will create a subdomain that points to my VPS IP address. Log into the client space of your hosting platform and do that. I use OVH.

Create a subdomain for our Web application

The change can take up to 24 hours to propagate. You can use this website to check if the DNS is ready.

Now we need Β to create an Nginx configuration for the website with the command below:

sudo nano /etc/nginx/sites-available/node.tericcabrel.com

Note: The file's name is node.tericcabrel.com I usually name it like that to quickly find the application I want to apply for a change. Feel free to call it as you wish.

Paste the code below inside the file, save and exit:

server {
    server_name  node.tericcabrel.com;
    index index.html index.htm;
    access_log /var/log/nginx/nodeapp.log;
    error_log  /var/log/nginx/nodeapp-error.log error;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:4500;
        proxy_redirect off;
    }
}

Don't forget to replace node.tericcabrel.com by your domain or subdomain.

Enable the website with the command below:

sudo ln -s /etc/nginx/sites-available/node.tericcabrel.com /etc/nginx/sites-enabled/node.tericcabrel.com

We created a symbolic link of our config file inside the folder sites-enabled it is because Nginx takes into account the website only present in this folder.

Let's verify if there is no Nginx error, then reload it to take the changes into account:

sudo nginx -t

sudo nginx -s reload

Navigate to node.tericcabrel.com to see the result:

The Node.js application accessible through the web

Yess, it works πŸŽ‰

Add SSL certificate with Letsencrypt

Install Certbot, which is the tool responsible for certificate generation:

sudo apt install snapd
sudo snap install --classic certbot

Generate and install an SSL certificate for our domain

sudo certbot --nginx -d node.tericcabrel.com

You will be asked if you want to redirect HTTP to HTTPS. You can press 1 to deny or 2 to accept. In this case, press 2, hit Enter, and wait for the installation to complete.

Reload Nginx configuration: sudo nginx -s reload

Navigate to https://node.tericcabrel.com.

Secure the web application with an SSL certificate

Challenge

Now, you know how to deploy a Node.js application with PM2 and Nginx, but before you go, I have a challenge for you:

Let's make the number of calls on node.tericcabrel.com reach 500000 πŸ˜†

You can find the code source for this tutorial on the GitHub repository.

Follow me on Twitter or subscribe to my newsletter to not miss the upcoming posts and the tips and tricks I share every week.

Happy to see you soon πŸ˜‰