Build Your Express.js App Server on Ubuntu: A Production-Ready Guide

Introduction

Node.js is a versatile JavaScript runtime that excels in building server-side and networking applications. Its cross-platform nature, supporting Linux, macOS, FreeBSD, and Windows, makes it a favorite among developers. While running Node.js applications directly from the command line is feasible for development, deploying them as a service is crucial for production environments. This ensures automatic restarts upon reboots or failures, making your applications robust and reliable.

This tutorial focuses on setting up a production-grade environment for your Express.js applications on an Ubuntu server. Express.js, a minimalist web application framework for Node.js, is widely used for building robust APIs and web applications. We will guide you through deploying an Express.js application on Ubuntu, managing it with PM2 for process management, and securing access via an Nginx reverse proxy. To ensure secure communication, we’ll also implement HTTPS using a free SSL certificate from Let’s Encrypt. This setup provides a solid foundation for hosting your Express.js applications in a production setting.

Prerequisites

Before you begin, ensure you have the following:

  • An Ubuntu 18.04 server: You should have access to an Ubuntu 18.04 server with a non-root user with sudo privileges and a firewall enabled.
  • A domain name pointed at your server: For this tutorial, we’ll assume your domain name is example.com. You will need to have this domain pointed to your server’s public IP address.
  • Nginx installed: Nginx should be installed on your server, and you should have a server block configured for your domain. You can follow the “Initial Server Setup with Ubuntu 18.04” and “How To Install Nginx on Ubuntu 18.04” guides for setting this up.

Upon completing these prerequisites, you should have Nginx serving your domain’s default placeholder page, accessible at https://example.com/.

Step 1 — Installing Node.js and npm

Our first step is to install Node.js and npm (Node Package Manager) on your Ubuntu 18.04 server. We will use NodeSource package archives to install the latest LTS (Long Term Support) version of Node.js.

Begin by adding the NodeSource PPA to your system. This repository provides updated packages for Node.js. Ensure you are in your home directory and use curl to download the setup script for Node.js 8.x:

<ol><li data-prefix="$"><span>cd</span> ~ </li><li data-prefix="$"><span>curl</span> <span>-sL</span> https://deb.nodesource.com/setup_8.x <span>-o</span> nodesource_setup.sh </li></ol>

Before running the script, it’s a good practice to inspect its contents to understand what changes it will make to your system. You can use nano or any text editor to view the script:

<ol><li data-prefix="$"><span>nano</span> nodesource_setup.sh </li></ol>

Once you are satisfied with the script’s contents, execute it using sudo:

<ol><li data-prefix="$"><span>sudo</span> <span>bash</span> nodesource_setup.sh </li></ol>

This command adds the PPA to your system’s package sources and updates your package cache. With the NodeSource repository configured, you can now install Node.js:

<ol><li data-prefix="$"><span>sudo</span> <span>apt</span> <span>install</span> nodejs </li></ol>

Verify the installed Node.js version by running:

<ol><li data-prefix="$">nodejs <span>-v</span> </li></ol>
<div title="Output">Output</div>v8.11.3

Note: When installed via the NodeSource PPA, the Node.js executable is named nodejs, not node.

The nodejs package includes both the nodejs binary and npm, eliminating the need for separate npm installation.

To confirm npm is installed and to create npm configuration files in your home directory (which happens on first run), execute:

<ol><li data-prefix="$"><span>npm</span> <span>-v</span> </li></ol>
<div title="Output">Output</div>5.6.0

For some npm packages that require compiling code from source, you will need to install the build-essential package:

<ol><li data-prefix="$"><span>sudo</span> <span>apt</span> <span>install</span> build-essential </li></ol>

Now you have successfully installed Node.js and npm, along with essential build tools, on your Ubuntu server. Let’s proceed to create an Express.js application.

Step 2 — Creating Your Express.js Application

Let’s create a basic “Hello World” Express.js application. This will serve as a simple test to ensure your Node.js and Express.js setup is working correctly. You can later replace this with your own application.

First, create a directory for your application and navigate into it:

<ol><li data-prefix="$"><span>mkdir</span> my-express-app </li><li data-prefix="$"><span>cd</span> my-express-app </li></ol>

Initialize a new npm project in this directory. This will create a package.json file for managing your project dependencies:

<ol><li data-prefix="$"><span>npm</span> init <span>-y</span> </li></ol>

Now, install Express.js as a dependency for your application:

<ol><li data-prefix="$"><span>npm</span> <span>install</span> express <span>--save</span> </li></ol>

Create a file named app.js. This will be the entry point for your Express.js application:

<ol><li data-prefix="$"><span>nano</span> app.js </li></ol>

Open app.js in a text editor and add the following code, which sets up a simple Express.js server that responds with “Hello World!” to HTTP GET requests at the root path:

~/my-express-app/app.js

<span>const</span> express <span>=</span> <span>require</span><span>(</span><span>'express'</span><span>)</span><span>;</span> <span>const</span> app <span>=</span> express<span>(</span><span>)</span><span>;</span> <span>const</span> port <span>=</span> <span>3000</span><span>;</span> app<span>.</span><span>get</span><span>(</span><span>'/'</span><span>,</span> <span>(</span><span>req<span>,</span> res</span><span>)</span> <span>=></span> <span>{</span> res<span>.</span><span>send</span><span>(</span><span>'Hello World!'</span><span>)</span><span>;</span> <span>}</span><span>)</span><span>;</span> app<span>.</span><span>listen</span><span>(</span>port<span>,</span> <span>(</span><span>)</span> <span>=></span> <span>{</span> <span>console</span><span>.</span><span>log</span><span>(</span><span><span>`</span><span>Example app listening at http://localhost:</span><span><span>${</span>port<span>}</span></span><span>`</span></span><span>)</span><span>;</span> <span>}</span><span>)</span><span>;</span>

Save the file and exit the editor.

This Express.js application defines a route for the root path (/) that sends a “Hello World!” response. It listens on port 3000 and logs a message to the console when started.

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

<ol><li data-prefix="$"><span>node</span> app.js </li></ol>

You should see the following output:

<div title="Output">Output</div>Example app listening at http://localhost:3000

To verify the application is working, open another terminal session and use curl to access localhost on port 3000:

<ol><li data-prefix="$"><span>curl</span> http://localhost:<mark>3000</mark> </li></ol>

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

<div title="Output">Output</div>Hello World!

Once you have confirmed it is working, stop the application by pressing CTRL+C in the terminal where it is running.

Step 3 — Installing and Setting Up PM2

To ensure your Express.js application runs reliably in production, we will use PM2, a process manager for Node.js applications. PM2 allows you to run applications in the background as services, automatically restarting them if they crash or upon server reboot.

Install PM2 globally using npm:

<ol><li data-prefix="$"><span>sudo</span> <span>npm</span> <span>install</span> pm2@latest <span>-g</span> </li></ol>

The -g flag installs PM2 globally, making it accessible system-wide.

Start your Express.js application, app.js, using PM2:

<ol><li data-prefix="$">pm2 start app.js </li></ol>

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

<div title="Output">Output</div>[PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2 [PM2] PM2 Successfully daemonized [PM2] Starting /home/sammy/my-express-app/app.js in fork_mode (1 instance) [PM2] Done. ┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤ │ <mark>app</mark> │ 0 │ fork │ 1432 │ online │ 0 │ 0s │ 0% │ 30.3 MB │ sammy │ disabled │ └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘ Use `pm2 show <id>` to get more details about an app </id>

PM2 automatically names the application based on the filename (app in this case) and assigns it a unique ID. It also displays process information like PID, status, restarts, uptime, and resource usage.

To ensure your Express.js application starts automatically on system boot, use the pm2 startup command. This generates and configures a system startup script:

<ol><li data-prefix="$">pm2 startup systemd </li></ol>

The output will provide a command that you need to run with superuser privileges to set up PM2 to start on boot. It will look similar to this:

<div title="Output">Output</div>[PM2] Init System found: systemd [PM2] To setup the Startup Script, copy/paste the following command: <mark>sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy</mark>

Execute the provided command, replacing <mark>sammy</mark> with your username:

<ol><li data-prefix="$"><span>sudo</span> <span>env</span> <span><span>PATH</span></span><span>=</span><span>$PATH</span>:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd <span>-u</span> <mark>sammy</mark> <span>--hp</span> /home/<mark>sammy</mark> </li></ol>

Save the current PM2 process list to ensure your application starts on reboot:

<ol><li data-prefix="$">pm2 save </li></ol>

Now, PM2 is configured to manage your Express.js application as a service, ensuring it restarts automatically on crashes and system reboots.

You can manage your application using PM2 commands. Here are a few useful ones:

  • Stop your application:

    <ol><li data-prefix="$">pm2 stop <mark>app</mark> </li></ol>
  • Restart your application:

    <ol><li data-prefix="$">pm2 restart <mark>app</mark> </li></ol>
  • List applications managed by PM2:

    <ol><li data-prefix="$">pm2 list </li></ol>
  • Get detailed information about an application:

    <ol><li data-prefix="$">pm2 info <mark>app</mark> </li></ol>
  • Monitor application metrics in real-time:

    <ol><li data-prefix="$">pm2 monit </li></ol>

With PM2 set up and managing your Express.js application, the next step is to configure Nginx as a reverse proxy to make your application accessible over the web.

Step 4 — Configuring Nginx as a Reverse Proxy

Currently, your Express.js application is running and accessible on localhost:3000. To make it accessible from the internet, we will configure Nginx as a reverse proxy. Nginx will receive HTTP requests from the internet and forward them to your Express.js application.

Open the Nginx server block configuration file for your domain. Assuming you configured it in the prerequisites, it should be located at /etc/nginx/sites-available/example.com:

<ol><li data-prefix="$"><span>sudo</span> <span>nano</span> /etc/nginx/sites-available/<mark>example.com</mark> </li></ol>

Within the server block, locate the location / block and replace its content with the following. Ensure the proxy_pass directive points to the port your Express.js application is listening on (port 3000 in this case):

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

server {
    ...

    location / {
        proxy_pass http://localhost:<mark>3000</mark>;
        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 the root path (/) of your domain to your Express.js application running on localhost:3000.

If you plan to host multiple applications on the same server, you can add additional location blocks within the same server block. For example, to proxy requests to another Express.js application running on port 3001 under the /api path, you would add:

/etc/nginx/sites-available/example.com — Optional

server {
    ...

    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;
    }

    location /api/ {
        proxy_pass http://localhost:<mark>3001</mark>;
        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 configuring the location blocks for your applications, save the file and exit the editor.

Test the Nginx configuration for syntax errors:

<ol><li data-prefix="$"><span>sudo</span> nginx <span>-t</span> </li></ol>

If the configuration is correct, reload Nginx to apply the changes:

<ol><li data-prefix="$"><span>sudo</span> systemctl reload nginx </li></ol>

Now, access your domain name (https://example.com) in a web browser. You should see the “Hello World!” message from your Express.js application, confirming that Nginx is successfully proxying requests to your application.

Conclusion

Congratulations! You have successfully set up a production-ready environment for your Express.js application on Ubuntu 18.04. Your application is now:

  • Running on an Ubuntu server.
  • Powered by Node.js and Express.js.
  • Managed by PM2 for reliability and automatic restarts.
  • Accessible via Nginx reverse proxy.

This setup provides a robust and scalable foundation for deploying and managing your Express.js applications in a production environment. You can now expand this setup by adding more features, securing your application further, and deploying your custom Express.js application logic.

Thank you for following this tutorial. Explore more about server management and web development with rental-server.net’s resources.

Learn more about our server solutions

About the author(s)

Brennen Bearnes

See author profile

Kathleen Juell

See author profile

Category:Tutorial

Tags:Node.js
Express.js
Nginx
Ubuntu 18.04

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 *