Deploy React App & Nestjs App to AWS EC2 Instance

Deploy React App & Nestjs App to AWS EC2 Instance

TABLE OF CONTENT

⚙️ Introduction

🧰 What You Need

🛠 Setup EC2

🪢 Setup CI/CD Pipeline

⛓ Setup Nestjs & React

📋 Conclusion

🔗 Helpful Links

⚙️ INTRODUCTION

Deploying modern web applications involves more than just writing code; it requires a streamlined process to ensure that applications are reliably built, tested, and deployed. In this article, we will guide you through the deployment of a React front-end application and a NestJS back-end application to an AWS EC2 instance. By leveraging CI/CD pipelines with GitHub Actions, we will demonstrate how to automate the deployment process, ensuring that your applications are consistently and efficiently delivered to users.

AWS EC2 provides a scalable, customizable environment that can host both your front-end and back-end applications. React, a popular JavaScript library for building user interfaces, and NestJS, a progressive Node.js framework for building efficient and scalable server-side applications, are a powerful combination for modern web development.

We will start by setting up our AWS EC2 instance, configuring the necessary environments, and deploying our applications. We will then dive into setting up GitHub Actions, illustrating how to create a CI/CD pipeline that automates the build and deployment process. This approach not only saves time but also minimizes human error, ensuring a seamless and repeatable deployment strategy.

By the end of this article, you will have a robust deployment workflow for your React and NestJS applications, empowering you to focus more on development and innovation, and less on the intricacies of manual deployments. Whether you’re a seasoned developer or just getting started with cloud deployments, this guide will provide you with the knowledge and tools to enhance your deployment practices. Let’s dive in!

🧰 WHAT YOU NEED

Nodejs/Nestjs project
React app project
Github account
AWS account
Basic understanding of javascript, AWS, and git/github

🛠 SETUP EC2

Login to your AWS account and create an EC2 instance. Don’t forget to save your keypair .pem file and allow inbound traffic for http and ssh for your security group. Now login to your instance using Instance connect or Ssh Client.

If using ssh client, use the coode block below. The first line is to change permissions for your keypair so it’s not public. The next line is to actually connect. The actual command depends on the AMI image your instance uses. Sometimes it’s root or ec2-user, then the public ip address of your instance preceded by ‘ec2-‘, and then the region.

chmod 400 keypair.pem
ssh -i “keypair.pem” ubuntu@ec2-12-345-678-9.eu-west-2.compute.amazonaws.com

If you have any challenges at this step, visit the helpful links section below to view step-by-step instructions from the official documentations.

Installing Nginx

We would be using Nginx as our reverse proxy. Reverse proxies are important for many reasons, one of which is it adds a layer of security to protect our web servers. Run the commands below:

sudo apt update
sudo apt install nginx -y

Installing Nodejs

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash –
sudo apt install -y nodejs
node -v

You should get a node version after the last line is run.

Now we can install pm2 and serve globally. We’d need them later:

npm i -g pm2
npm i -g serve

PM2 is a production process manager for Node.js apps, ensuring they run continuously and efficiently. It handles tasks like restarting crashed applications, load balancing in cluster mode, and managing logs. PM2 simplifies monitoring and maintenance, making it easier to keep applications stable and performant in production.

Serve is a simple command-line HTTP server for serving static files. It’s easy to use, allowing you to quickly host and view your static site or single-page application. With minimal setup, serve efficiently delivers your content, making it a handy tool for development and testing environments.

🪢 SETUP CI/CD PIPELINE

CI/CD is a very robust subject, but for the purposes of this guide, I’d summarize it. We’d be using Github actions, feel free to read more from their documentation in the link at the end.

CI/CD involves continuous integration, where code changes are automatically tested and merged, and continuous deployment, where these changes are automatically released to production. In GitHub Actions, workflows are defined in YAML files and consist of jobs triggered by events like code pushes. These jobs, which are executed by runners, contain steps that perform tasks such as testing, building, and deploying applications. This setup ensures efficient, reliable, and automated software delivery.

As shown above, go to the settings tab of your repository, and click on runners on the sub navigation. We’d be using self-hosted runners that we’d run on our EC2 instance. The list of supported OS that support self-hosted runners can be found here

Choose the OS that your ec2 instance uses, and just run the commands. The commands would install github runner on your instance and it would be linked to your github repository. Don’t forget to give the runner a unique name, and add ‘backend-runner’ as a label for the runner.

Also run the code below to start actions runner as a service:

sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status

Now go to Actions, and click the ‘New Workflow button’.

Search with keyword ‘node’, and select highlighted option. Edit the code you see there and replace with the code below:

name: Node.js CI/CD

on:
push:
branches: [ main” ]
workflow_dispatch:

jobs:
build:
# runs-on: self-hosted
runs-on: backend-runner
strategy:
matrix:
node-version: [20.x]
steps:
name: Cleanup build folder’
run: sudo rm -r ${{github.workspace}}/*
uses: actions/checkout@v3
name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm’
run: sudo npm install
run: sudo npm run build
run: pm2 start pm2.config.js

Explanation: This workflow would be run when any changes are pushed to the main branch of your repository. Workflow_dispatch means the workflow can also be triggered manually. Ideally, you should run on self-hosted for self hosted runners as we are implementing. But we intend to run two self-hosted runner, one for the react app, and the other for the nestjs app, so we use the runner label to differentiate. We are yet to create our pm2.config.js file, which we will do next.

Duplicate the above steps for the repository with the react app. Make sure the runner names are different, as directories are being created on the ec2 instance for each runner.

The code below would be the workflow for the react app:

name: React.js CI/CD

on:
push:
branches: [ main” ]
workflow_dispatch:

jobs:
build:
# runs-on: self-hosted
runs-on: frontend-runner
strategy:
matrix:
node-version: [20.x]

steps:
name: Cleanup build folder’
run: sudo rm -r ${{github.workspace}}/*
uses: actions/checkout@v3
name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm’
# – run: sudo yarn install
run: sudo npm ci
run: vite build
# – run: npm i -g serve
run: sudo chmod 777 /home/ubuntu/frontend-runner/_work/Website/Website/node_modules/
run: sudo chown -R ubuntu:ubuntu /home/ubuntu/frontend-runner/_work/Website/Website/node_modules/
run: pm2 start pm2.config.cjs

Explanation: Same explanation here, runs on frontend-runner which is a unique label for the react runner. The two extra run commands are for optional. If you run into permission issues while running the workflow, the first optional command give all users read, write and execute access for node_modules. The second command makes your user to be owner of node_modules. Then we finally run the pm2.config.cjs file.

We can test to see that our two apps are running by running the pm2 list command. The response should look like below:

Nginx Cofiguration

While still in your instance, enter the command below:

sudo nano /etc/nginx/sites-available/default

Now enter the code below into the editor.

server {

root /var/www/html;

# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;

listen 80;
server_name ec2-instance-ip-address;

location / {

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_pass http://localhost:5173;
proxy_read_timeout 90;

# WebSocket support if using vite bundler
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;

}

location /api/ {

add_header ‘Access-Control-Allow-Origin’ ‘*’ always;
add_header ‘Access-Control-Allow-Methods’ ‘GET, POST, PUT, DELETE, OPTIONS’ always;
add_header ‘Access-Control-Allow-Headers’ ‘Origin, X-Requested-With, Content-Type, Accept’ always;
add_header ‘Access-Control-Allow-Credentials’ ‘true’ always;

proxy_pass http://localhost:3002;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

}

}

Then run sudo nginx -t to test your changes, then sudo systemctl restart nginx to restart the nginx server.

Explanation: We have two separate locations in our server, / for the react app, /api/ for nestjs app. Headers are added to api location to handle cors errors. If you bundle your react app with vite, you need to add websockets support. Finally, make sure your proxy_pass is running on the right ports for both app.

🪢 SETUP NESTJS & REACT

In our nestjs package.json, the scripts portion looks like below:

scripts: {
prebuild: rimraf dist,
build: nest build,
format: prettier –write src/**/*.ts test/**/*.ts,
start: nest start,
start:dev: nest start –watch,
start:debug: nest start –debug –watch,
start:prod: node dist/main,
lint: eslint {src,apps,libs,test}/**/*.ts –fix,
test: jest,
test:watch: jest –watch,
test:cov: jest –coverage,
test:debug: node –inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest –runInBand,
test:e2e: jest –config ./test/jest-e2e.json
},

And finally, our pm2.config.js file

module.exports = {
apps: [
{
name: backend-app,
script: npm,
args: run start:prod,
autorestart: true,
watch: false,
max_memory_restart: 300M,
},
],
};

React Setup

In our react package.json, the scripts portion looks like below:

scripts: {
dev: vite –host,
prod: npx serve dist -s -p 5173,
build: tsc && vite build,
lint: eslint . –ext ts,tsx –report-unused-disable-directives –max-warnings 0,
preview: vite preview,
},

And our pm2.config.cjs file

module.exports = {
apps: [
{
name: frontend-app
script: npm,
args: run prod,
autorestart: true,
watch: false,
max_memory_restart: 300M,
},
],
};

📋 CONCLUSION

Deploying a React and NestJS application on an AWS EC2 instance with a seamless CI/CD pipeline using GitHub Actions is a powerful demonstration of modern web development practices. By leveraging the scalability and flexibility of AWS, combined with the automation capabilities of GitHub Actions, you can ensure efficient deployment and continuous integration. The use of Nginx to serve both the frontend and backend further streamlines the process, providing a robust solution for handling web traffic and API requests. This setup not only enhances the reliability and performance of your applications but also simplifies the deployment workflow, making it easier to manage updates and scale your infrastructure as needed. Embracing these technologies and best practices paves the way for more efficient, maintainable, and scalable software development.

HAPPY CODING!!!🚀🚀

(Article was written with input from ChatGPT)

### 🔗 HELPFUL LINKS

Setup Nestjs project – https://docs.nestjs.com/first-steps

Setup React project – https://react.dev/learn/start-a-new-react-project

Setup EC2 Instance – https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html

Setup Github Actions –
https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners

Nginx Reverse Proxy – https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/

Please follow and like us:
Pin Share