DEV Community

Cover image for How to Build and Deploy a Node.js REST API on AWS EC2 with PM2
anitaalicloud
anitaalicloud

Posted on

How to Build and Deploy a Node.js REST API on AWS EC2 with PM2

Introduction

Knowing how to get an API from your local machine to a live, publicly accessible server is one of the most practical skills in a developer or DevOps engineer's toolkit. In this guide, I'll walk you through the full process, from writing a minimal Express API to deploying it on AWS EC2 with PM2 keeping it alive persistently.

By the end, you'll have a running API accessible from any browser or HTTP client, with no manual restarts required.


Prerequisites

  • An AWS account (free tier works)
  • Node.js installed locally
  • Basic familiarity with the Linux command line
  • A GitHub account

What We're Building

A lightweight three-endpoint JSON API:

Endpoint Response
GET / { "message": "API is running" }
GET /health { "message": "healthy" }
GET /me Name, email, and GitHub link

All endpoints return Content-Type: application/json, HTTP 200, and respond in well under 500ms.


Step 1: Writing the API

On your local machine, initialize a new Node.js project and install Express:

mkdir personal-api
cd personal-api
npm init -y
npm install express
Enter fullscreen mode Exit fullscreen mode

Create index.js:

const express = require("express");
const app = express();
const PORT = 3000;

app.get("/", (req, res) => {
  res.status(200).json({ message: "API is running" });
});

app.get("/health", (req, res) => {
  res.status(200).json({ message: "healthy" });
});

app.get("/me", (req, res) => {
  res.status(200).json({
    name: "Your Full Name",
    email: "you@example.com",
    github: "https://github.com/yourusername",
  });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Test it locally:

node index.js
curl http://localhost:3000/
curl http://localhost:3000/health
curl http://localhost:3000/me
Enter fullscreen mode Exit fullscreen mode

All three should return the expected JSON with a 200 status. ✅


Step 2: Push to GitHub

Create a public repository and push your code:

git init
git add .
git commit -m "Initial commit: personal API"
git remote add origin https://github.com/YOUR_USERNAME/personal-api.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Step 3: Provision an AWS EC2 Instance

  1. Launch a t2.micro (free tier eligible) instance with Ubuntu 22.04 LTS
  2. Create or reuse a key pair for SSH
  3. In the Security Group, open inbound traffic on:
    • Port 22 (SSH)
    • Port 80 (HTTP)


ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up the Server Environment

Install Node.js

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
Enter fullscreen mode Exit fullscreen mode

Clone the Repo and Install Dependencies

cd ~
git clone https://github.com/YOUR_USERNAME/personal-api.git
cd personal-api
npm install
Enter fullscreen mode Exit fullscreen mode

Quick sanity check:

node index.js
Enter fullscreen mode Exit fullscreen mode

Step 5: Keep the App Running with PM2

If you start the app with node index.js and close your SSH session, the process dies. PM2 solves this — it's a production process manager that restarts your app on crashes and survives server reboots.

Install PM2

sudo npm install -g pm2
Enter fullscreen mode Exit fullscreen mode

Start the App

pm2 start index.js --name personal-api
Enter fullscreen mode Exit fullscreen mode

Enable PM2 on Boot

pm2 startup
# Run the command it outputs, then:
pm2 save
Enter fullscreen mode Exit fullscreen mode

From now on, the app restarts automatically after any reboot or crash.

Verify:

pm2 list
Enter fullscreen mode Exit fullscreen mode

Step 6: Configure Nginx as a Reverse Proxy

The app runs on port 3000 internally. Rather than exposing that port to the public, we use Nginx to listen on port 80 and forward requests to the app. This is the standard production pattern.

Install Nginx

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx
Enter fullscreen mode Exit fullscreen mode

Create the Site Config

sudo nano /etc/nginx/sites-available/personal-api
Enter fullscreen mode Exit fullscreen mode

Paste the following (replace YOUR_EC2_PUBLIC_IP with your actual IP or domain):

server {
    listen 80;
    server_name YOUR_EC2_PUBLIC_IP;

    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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Enable and Reload

sudo ln -s /etc/nginx/sites-available/personal-api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

This should be your expected result.


Step 7: Test the Live Deployment

curl http://YOUR_EC2_PUBLIC_IP/
curl http://YOUR_EC2_PUBLIC_IP/health
curl http://YOUR_EC2_PUBLIC_IP/me
Enter fullscreen mode Exit fullscreen mode

All three return the correct JSON over HTTP. ✅

Final Project Structure

personal-api/
├── index.js        # Express API
├── package.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Never expose your app port directly. Nginx as a reverse proxy adds a layer of control and is the correct production pattern — it also makes it easy to host multiple apps on one server later.
  • PM2 is non-negotiable for Node.js in production. Without a process manager, one crash or reboot silently kills your service.
  • Always test locally first. It's much faster to debug on your machine than on a remote server.
  • EC2 security groups are your firewall. By default, everything is blocked — be deliberate about what you open.

Live Demo

GitHub Repo: https://github.com/AnitaAliCloud/hng-stage1-api

Top comments (0)