How to Build a Server Using Express.js on Ubuntu: A Comprehensive Guide

Introduction

Node.js has become a cornerstone for modern web development, providing a robust JavaScript runtime environment ideal for server-side and networking applications. Its cross-platform nature, compatible with Linux, macOS, FreeBSD, and Windows, makes it a versatile choice for developers. While running Node.js applications directly from the command line is possible, deploying them as a service is crucial for production environments. This ensures automatic restarts upon system reboots or failures, enhancing reliability.

This tutorial will guide you through setting up a production-ready Node.js server on Ubuntu, focusing on using Express.js – a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. We will manage our Express.js application using PM2, a process manager designed for Node.js applications, and provide secure, public access via an Nginx reverse proxy. To ensure security, we will also implement HTTPS using a free SSL certificate from Let’s Encrypt.

This approach is perfect for anyone looking to deploy web applications or APIs built with Express.js on a reliable Ubuntu server.

Prerequisites

Before starting this guide, ensure you have the following:

  • An Ubuntu 20.04 server: You will need access to an Ubuntu 20.04 server. If you don’t have one already, consider using a cloud provider that offers Ubuntu servers.
  • A domain name configured to point to your server: For this tutorial, we will assume you have a domain name, such as example.com, pointed to your server’s public IP address. You should have this domain’s default Nginx placeholder page accessible at https://example.com/ as a starting point.
  • Nginx installed and configured with server blocks: Follow the initial server setup tutorial for Ubuntu 20.04, including setting up a basic Nginx server block for your domain.

Once you have these prerequisites in place, you are ready to proceed with building your Express.js server.

Step 1 — Installing Node.js and npm

To begin, we need to install Node.js and npm (Node Package Manager) on your Ubuntu 20.04 server. We will use NodeSource package archives to install the latest Long-Term Support (LTS) version of Node.js, ensuring stability and reliability for production.

First, add the NodeSource PPA (Personal Package Archive). Navigate to your home directory and use curl to fetch the installation script for the latest LTS Node.js version:

cd ~
curl -sL https://deb.nodesource.com/setup_lts.x -o nodesource_setup.sh

It’s always a good practice to inspect scripts downloaded from the internet before running them. You can examine the contents of the nodesource_setup.sh script using nano or your preferred text editor:

nano nodesource_setup.sh

After reviewing the script, execute it with sudo to add the PPA to your system’s package sources and update your package cache:

sudo bash nodesource_setup.sh

With the NodeSource PPA configured, you can now install the nodejs package, which includes both the nodejs runtime and npm:

sudo apt install nodejs

Verify the installation by checking the installed Node.js version:

node -v

You should see output similar to:

v18.x.x

Note: When installed via NodeSource PPA, the Node.js executable is named nodejs. However, many Node.js tutorials and documentation assume it’s named node. For convenience, you can create an alias: alias node=nodejs. However, in most cases, nodejs will work directly or you might find node already aliased. In this tutorial, we’ll use node assuming it points to your Node.js installation.

Next, verify that npm is also installed and functional. This command also creates npm configuration files in your home directory if they don’t already exist:

npm -v

You should see the installed npm version, like:

9.x.x

Finally, to compile some npm packages that require building code from source (often native addons), install the build-essential package:

sudo apt install build-essential

With Node.js and npm successfully installed, we can now proceed to create our Express.js application.

Step 2 — Creating an Express.js Application

Now, let’s build a basic “Hello World” application using Express.js. This will serve as a simple example to ensure our setup is correct. You can later replace this with your own, more complex application.

First, create a project directory for your application. We will name it express-hello-world:

cd ~
mkdir express-hello-world
cd express-hello-world

Initialize a new Node.js project using npm. This will create a package.json file to manage your project dependencies:

npm init -y

Now, install Express.js as a project dependency:

npm install express

Create a file named app.js (or any name you prefer for your main application file):

nano app.js

Paste the following code into app.js. This code sets up a basic Express.js server that listens on port 3000 and responds with “Hello World!” when you access the root path /:

const express = require('express');
const app = express();
const port = 3000;

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

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

Save the file and exit the editor.

This Express.js application defines a single route for the root path (/) that sends a “Hello World!” response. It then starts the server, listening on port 3000 on localhost.

To test your application, run it using Node.js:

node app.js

You should see the output:

Server listening at http://localhost:3000

To verify it’s working, open another terminal session on your server and use curl to access the application:

curl http://localhost:3000

If you see “Hello World!” as output, your Express.js application is functioning correctly.

Once confirmed, stop the application by pressing CTRL+C in the terminal where it’s running.

Step 3 — Setting Up PM2 Process Manager

For production deployments, it’s crucial to have a process manager that keeps your application running reliably. PM2 (Process Manager 2) is a popular choice for Node.js applications. It allows you to run applications in the background, monitor them, and automatically restart them if they crash or upon server reboot.

Install PM2 globally using npm:

sudo npm install pm2 -g

Start your Express.js application using PM2. Navigate back to your application directory if you’ve changed directories:

cd ~/express-hello-world
pm2 start app.js

PM2 will start your application and add it to its process list. You’ll see output similar to:

[PM2] Starting /home/your_user/express-hello-world/app.js in fork_mode (1 instance)
[PM2] Done.
┌───────────────┬──────────────┬──────────┬──────────┬───────────┬──────────┬──────────┐
│ App name      │ id           │ version  │ mode     │ pid       │ status   │ restart  │
│ ─────────────── ┴────────────── ┴────────── ┴────────── ┴─────────── ┴────────── ┴────────── │
│ express-hello-world │ 0            │ N/A      │ fork     │ 12345     │ online   │ 0        │
└─────────────────┴──────────────┴──────────┴──────────┴───────────┴──────────┴──────────┘

PM2 automatically assigns an “App name” (based on the filename) and a PM2 “id”. It also tracks process ID (PID), status, and other metrics.

To ensure your application starts automatically on server boot, use PM2’s startup command to generate and configure a system startup script:

pm2 startup systemd

The output will include a command to run with sudo to set up PM2 to start on boot. It will look something like this (replace your_user with your actual username):

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your_user --hp /home/your_user

Copy and execute this command.

Finally, save the current PM2 process list to ensure your application will be automatically restarted on boot:

pm2 save

Now, your Express.js application is managed by PM2 and will automatically restart if it crashes or when the server reboots.

PM2 provides several useful commands for managing your applications:

  • pm2 stop <app_name_or_id>: Stops an application.
  • pm2 restart <app_name_or_id>: Restarts an application.
  • pm2 list: Shows a list of applications managed by PM2.
  • pm2 info <app_name_or_id>: Provides detailed information about a specific application.
  • pm2 monit: Opens a process monitoring dashboard in the terminal.

Step 4 — Configuring Nginx as a Reverse Proxy

Our Express.js application is running on localhost:3000, but we need to make it accessible from the internet via our domain name. We will use Nginx as a reverse proxy to forward requests from the public internet to our application.

Open your Nginx server block configuration file for your domain. This is usually located at /etc/nginx/sites-available/example.com (replace example.com with your actual domain):

sudo nano /etc/nginx/sites-available/example.com

Inside the server block, find the location / block. Replace its content with the following configuration to proxy requests to your Express.js application running on port 3000:

location / {
    proxy_pass http://localhost: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;
}

This configuration directs all requests to your domain’s root URL (/) to the Node.js application listening on http://localhost:3000.

If you want to host multiple Node.js applications on the same server, you can add additional location blocks within the same server block. For example, to proxy to another application running on port 3001 and accessible via https://example.com/app2, you would add:

location /app2 {
    proxy_pass http://localhost:3001;
    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;
}

After making changes, save the file and exit the editor.

Test your Nginx configuration for syntax errors:

sudo nginx -t

If the configuration is valid, restart Nginx to apply the changes:

sudo systemctl restart nginx

Now, access your domain name (https://example.com) in your web browser. You should see the “Hello World!” message from your Express.js application. If you configured the /app2 location, you can access the second application at https://example.com/app2.

Conclusion

Congratulations! You have successfully set up an Express.js server on Ubuntu 20.04, managed by PM2 and accessible via an Nginx reverse proxy. This setup provides a solid foundation for deploying production-ready Node.js applications. With Nginx acting as a reverse proxy, you can easily manage traffic, handle SSL termination, and even serve static content alongside your dynamic application.

This tutorial covered the basic setup. For production environments, consider exploring further configurations such as:

  • Setting up HTTPS with Let’s Encrypt: Secure your application with SSL/TLS encryption for secure communication.
  • Configuring environment variables: Manage configuration settings outside of your application code.
  • Implementing logging and monitoring: Gain insights into your application’s performance and identify potential issues.
  • Setting up a more robust Express.js application: Expand beyond the “Hello World!” example to build your desired web application or API.

By following these steps and further exploring the mentioned configurations, you can build and deploy scalable and reliable web applications using Express.js on Ubuntu.

Thank you for following this guide. Explore more about server management, Node.js, and Express.js to enhance your web development skills and server infrastructure.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *