How to Deploy a React App to AWS with Express, Postgres, PM2 and Nginx

In this in-depth tutorial, we‘ll walk through the process of deploying a production-ready React application to AWS. We‘ll be using a powerful stack consisting of a Node.js/Express backend server, a PostgreSQL database, PM2 for process management, and Nginx as a reverse proxy. By the end, you‘ll have a fully-functional web app running in the cloud.

Overview of the Technology Stack

Before diving into the deployment process, let‘s take a high-level look at the key technologies being used:

React

React is a popular JavaScript library for building interactive user interfaces. It allows you to create reusable UI components and efficiently update the DOM. We‘ll be using React for the frontend of our application.

Node.js and Express

Node.js is a JavaScript runtime built on Chrome‘s V8 engine, allowing you to run JavaScript on the server. Express is a minimal web framework for Node.js that provides a robust set of features for building web applications and APIs. Our backend server will be built with Express.

PostgreSQL and AWS RDS

PostgreSQL is a powerful open-source relational database known for its reliability, performance and rich feature set. We‘ll be using Postgres as our database and hosting it on Amazon RDS (Relational Database Service) for easy setup, scaling and maintenance.

Nginx

Nginx is a high-performance web server that can also act as a reverse proxy, load balancer and HTTP cache. We‘ll be using Nginx to proxy requests to our Node.js server and serve the static files for our React app.

PM2

PM2 is a production process manager for Node.js applications. It allows you to keep your app running in the background, automatically restarts it if it crashes, and provides features like logging and monitoring. We‘ll use PM2 to manage our Express server.

Here‘s a diagram illustrating how these components fit together:

[Architectural diagram showing React <-> Nginx <-> Express <-> Postgres on AWS]

Preparing the React App for Production

Before we can deploy our React app, we need to prepare it for production. This involves creating an optimized build and configuring an Express server to serve it.

Building the React App

To create a production-ready build of your React app, run the following command:

npm run build

This will generate an optimized bundle of your app in the build directory, with your code minified and ready to be served.

Setting Up the Express Server

Next, create a new file called `server.js` in the root of your project with the following code:

const express = require(‘express‘);
const path = require(‘path‘);

const app = express();

// Serve static files from the React app
app.use(express.static(path.join(__dirname, ‘build‘)));

// Serve the React app for all other routes
app.get(‘*‘, (req, res) => {
  res.sendFile(path.join(__dirname, ‘build‘, ‘index.html‘));
});

const port = process.env.PORT || 5000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

This sets up a basic Express server that serves the static files from the build directory and handles all other requests by returning the React index.html file. This allows React to handle the routing on the client-side.

Environment-Specific Configuration

Your React app may need different configuration values depending on the environment it‘s running in (development, staging, production, etc.). To handle this, you can create a `.env` file in your project root with environment-specific variables:

REACT_APP_API_URL=http://localhost:5000/api

Then access these variables in your React code like this:

const API_URL = process.env.REACT_APP_API_URL;

For the production environment, you‘ll set these values as environment variables on your EC2 instance rather than using the .env file.

Provisioning the AWS Infrastructure

With our app ready to be deployed, let‘s set up the necessary AWS resources.

Creating an EC2 Instance

First, launch a new EC2 instance to host your application. Choose an Amazon Machine Image (AMI) with Node.js support, such as the Amazon Linux 2 AMI. For the instance type, a t2.micro will suffice for a small application.

During the instance launch wizard, configure the following:

  • VPC: Choose the default VPC or create a new one
  • Subnet: Choose a public subnet so your instance is reachable from the internet
  • Auto-assign Public IP: Enable this to automatically assign a public IP to your instance
  • Security Group: Create a new security group allowing inbound traffic on ports 22 (SSH), 80 (HTTP), and 443 (HTTPS)

Configuring the RDS Database

Next, provision a new RDS instance for your Postgres database. In the RDS console, click "Create database" and select PostgreSQL as the engine type.

Configure the following settings:

  • Specify DB details: Choose a DB instance class, allocated storage, and master username/password
  • Connectivity: Select the same VPC as your EC2 instance
  • Additional configuration
    • Initial database name: Give your database a name
    • Backup: Enable automated backups for disaster recovery
    • Maintenance window: Choose a low-traffic time for automated maintenance/upgrades

Updating the Security Groups

To allow the EC2 instance to access the RDS database, you‘ll need to modify the security groups:

  1. Find the security group associated with your RDS instance
  2. Edit the inbound rules and add a new rule allowing PostgreSQL traffic (port 5432) from the EC2 instance‘s security group

This will enable the Express server on EC2 to connect to the Postgres database on RDS.

Deploying the Application

With our infrastructure ready, we can now deploy our application to the EC2 instance.

Installing Dependencies

SSH into your EC2 instance and install the required dependencies:

sudo yum update -y
sudo yum install -y gcc-c++ make
curl -sL https://rpm.nodesource.com/setup_14.x | sudo -E bash -
sudo yum install -y nodejs
sudo npm install -g pm2

This will update the package manager, install Node.js v14.x, and globally install PM2.

Transferring Application Files

Next, transfer your application files to the EC2 instance using `scp` or your preferred method. Assuming your files are in a directory called `my-app`, the command would look like this:

scp -i /path/to/key.pem -r my-app ec2-user@<public-ip>:/home/ec2-user/

Replace /path/to/key.pem with the path to your EC2 key pair file and <public-ip> with your instance‘s public IP address.

Setting Up Environment Variables

On your EC2 instance, set the necessary environment variables for your application. You can do this by editing the `~/.bashrc` file:

nano ~/.bashrc

Add the following lines at the bottom of the file:

export REACT_APP_API_URL=http://localhost:5000/api 
export DB_HOST=<rds-endpoint>
export DB_NAME=<database-name>
export DB_USER=<db-username>
export DB_PASSWORD=<db-password>

Replace the placeholders with your actual RDS database information. Save the file and run source ~/.bashrc to apply the changes.

Running Database Migrations

If your application requires a database schema, you‘ll need to run migrations on the RDS instance. Navigate to your application directory and run:

npx sequelize-cli db:migrate

Adjust the command based on your specific database migration setup.

Starting the Application with PM2

Finally, start your Express server using PM2:

cd my-app
pm2 start server.js

Your application should now be running and accessible via the EC2 instance‘s public IP.

Configuring Nginx

To complete the setup, we‘ll configure Nginx as a reverse proxy for our application.

Installing and Configuring Nginx

Install Nginx on your EC2 instance:

sudo amazon-linux-extras install nginx1.12

Edit the Nginx configuration file:

sudo nano /etc/nginx/nginx.conf

Replace the contents with the following:

events {
  worker_connections 1024;
}

http {
  upstream my_app {
    server 127.0.0.1:5000;
  }

  server {
    listen 80;
    server_name my-app.com;

    location / {
      proxy_pass http://my_app;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}

This configuration tells Nginx to listen on port 80 and proxy requests to your Express server running on 127.0.0.1:5000. Adjust the server_name to match your domain if you have one set up.

Serving React Static Files

To serve the React static files directly from Nginx for improved performance, you can modify the configuration like this:

server {
  listen 80;
  server_name my-app.com;

  root /home/ec2-user/my-app/build;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  location /api {
    proxy_pass http://my_app;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

This configuration serves files from the React build directory and only proxies requests starting with /api to the Express server.

Enabling HTTPS with SSL Certificates

To secure your application with HTTPS, you‘ll need to obtain an SSL/TLS certificate. You can either purchase one from a certificate authority or use a free service like Let‘s Encrypt.

Once you have your certificate files, modify the Nginx configuration to use HTTPS:

http {
  # ... existing configuration ...

  server {
    listen 443 ssl;
    server_name my-app.com;

    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/certificate.key;

    # ... existing server configuration ...
  }

  server {
    listen 80;
    server_name my-app.com;
    return 301 https://$server_name$request_uri;
  }
}

This configures Nginx to listen on port 443 for HTTPS traffic and redirects HTTP requests to HTTPS.

Restart Nginx for the changes to take effect:

sudo service nginx restart

Scaling and Monitoring

As your application grows, you may need to scale your infrastructure and monitor its performance. Here are a few strategies:

Using PM2 Cluster Mode

PM2 provides a cluster mode that allows you to take advantage of multi-core systems. To start your Express server in cluster mode, use the `-i` flag with the number of instances you want to run:

pm2 start server.js -i max

This will start one process per CPU core, allowing your application to handle more concurrent requests.

Configuring Auto Scaling

To automatically adjust the number of EC2 instances based on traffic, you can use AWS Auto Scaling. Create a launch template specifying the instance configuration and a scaling policy defining when to scale in or out based on metrics like CPU utilization.

Monitoring with CloudWatch

AWS CloudWatch allows you to monitor various metrics for your EC2 instances and set alarms based on thresholds. You can monitor CPU usage, network traffic, disk usage, and more. Set up alarms to notify you or trigger auto scaling actions when certain thresholds are breached.

Logging with PM2 and CloudWatch Logs

PM2 provides logging functionality out of the box. You can view logs with the `pm2 logs` command or configure PM2 to stream logs to AWS CloudWatch Logs for centralized log management and analysis.

Conclusion

Deploying a React application to AWS using Express, Postgres, PM2, and Nginx involves several steps, but provides a scalable and performant architecture. By leveraging AWS services like EC2 and RDS, you can focus on developing your application while benefiting from the reliability and flexibility of the cloud.

Remember to regularly monitor your application, optimize performance as needed, and keep your dependencies up to date. With the right setup and ongoing maintenance, your React app will be production-ready and able to handle growth.

I hope this in-depth tutorial has provided you with a solid foundation for deploying your own React applications to AWS. Happy deploying!

Similar Posts