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:
- Find the security group associated with your RDS instance
- 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!