Deploying React / Node.js Application to Amazon EC2 Instance

Step-by step guide to deploy front and back end of a React JS/ Node JS application to Amazon EC2 instance

When you are developing a React application, running the application on your local machine is an easy task, just npm run start and you are all set. But once the application is ready, and you want to put it for others to access, you face the challenges of where to host this application and how to do it. In large scale applications usually there is a dedicated resources to take care of that as part os the Devops team, but if you are writing a simple application that you just need to host it somewhere then Amazon EC2 provides a great affordable environment to perform this task.

The Scenario and Sample Application

For the purpose of this tutorial, we are assuming that you have a React JS application which fetches data from a back end NodeJS application, and you want to deploy both applications to Amazon.

You can work on your own application, or you can download the sample application I used for this tutorial from GitHub by following this link.

The provided sample application is simply a server that returns one line of static text, and a front end React application that fetches the value from the back end and display it.

You have to edit the /src/App.js in the client app with the value of the URL that you get for your public DNS record for the EC2 instance that you will create, you do not need to worry if this does not make sense now, it will make sense once you configure and start the instance.

You have to keep in mind that when a React application id built, the output is static HTML with some CSS and JS files, so you have to create a Node JS application in order to serve those files on the production environment, you can refer to the sample code for more explanation.

Create and Launch an Amazon EC2 Instance

The first step after you create an Amazon account is to launch a new EC2 instance, EC2 (or Elastic Compute) is a computing platform where you can rent virtual machines on, so when you create an instance, you are actually creating a new virtual machine that is running on one of Amazon’s massive data centres.

Once you login to the AWS Management Console, locate EC2 under the Compute section in the services list, or you can search for EC2 in the search box, click on EC2, this will take you to the EC2 Dashboard. From this dashboard you can create and manage your instance. To create a new instance, click on Launch Instance.

Then select the Amazon Machine Image (AMI) for the instance you want to launch. In plain English, here you have to decide what is the operating system you want on the instance you are launching. For the purpose of this tutorial, we will use Ubuntu, you can select a different operating system, but then the steps for preparing the instance can be different. Select the Ubuntu Server 18.04 LTS (HVM), SSD Volume Type, this is the latest available Ubuntu instance type at the time of writing this tutorial.

Next you will be prompted to select the instance type, which will decide the resources (RAM, CPU and Storage) available for this instance. We will select the General purpose t2.macro, which is eligible for the free tier of AWS.

Once the instance type is selected, click on (Next: Configure Instance Details). You can leave everything on its default value and click on (Next: Add Storage), this will take you the screen where you can configure the storage available for the newly created instance, again leave everything on its default value and click (Next: Add Tags).
We won’t be adding any tags at the moment, so you can click on (Next: Configure Security Group).

On the configure Security Group screen, you can manage the network ports that are exposed for this instance, by default you will have TCP port 22 open to enable you to connect to the instance via SSH.
Click on Add Rule and add an entry for the front-end application (which is running on port 3000 in our example) and the back-end application (which is running on port 4000 in our example). For both entries you should select (Anywhere) in the sources drop-down, as we want those applications to be accessible from anywhere. You will get a warning that the instance will be accessible from any external source, but that’s ok since we want our application to be public and accessible. You can provide a name and a description for the set of rules you are defining, you can provide a descriptive name and detail, but for this tutorial we will just leave the default values.

Once done click on (Review and Launch), review the details of the instance and click on Launch. You will be prompted to create a key pair file to access this instance, this will be similar to the key that you can use whenever you want to access the instance, you should download this key and keep it in a secure location, you can’t access the instance without this file, and if someone else got the file they will be able to access your instance. Beware that beyond this time you will not have the option to download the key pair again, so you have to make sure you save the downloaded file in a secure location and never loose it. In case the file is lost in the future there is an option to attach a new key par, but it’s a multi-step process that require you to restart the instance.

Provide a name for the file then click on (Download Key Pair). Once done, click on (Launch Instance) to start your instance.

It will take a little time for Amazon to do its magic to create the new instance and launch it, after a minute or so, the status will turn in (running) and the instance will be ready to be used.

You will need to obtain the public DNS of the instance in order to be able to connect to it and transfer files to it in a later step. From the instance details page, look for the Public DNS (IPv4) and copy the value to use it in further steps, for the purpose of this tutorial, we will assume that the instance public DNS is ec2-1-1-1-1.compute-1.amazonaws.com. This is a fictitious entry so do not try to connect to it :).

This public DNS record should be updated in the provided sample app in the file /src/App.js.

Connecting to the Instance Through SSH

You need to connect to the newly created instance through SSH in order to manage the instance and install the required packages.

If you are on a MAC, first step is to save the key-pair file to your .ssh directory, to do that, open the terminal and use the command open ~/.ssh to open the .ssh folder in finder. Once opened, copy and paste your key par file that you downloaded while creating the EC2 instance on Amazon. For the purpose of this tutorial we will assume that the key par filename is MyInstance.pem.

If you are on a linux based machine, you have to secure the key pair file by running the following command:

chmod 400 ~/.ssh/MyInstance.pem

In order to connect to the instance, use the following command (remember to change the key pair file name and the instance name with the actual values from the instance that you created):

ssh -i "~/.ssh/MyInstance.pem" ubuntu@ec2-1-1-1-1.us-east-2.compute.amazonaws.com

You will be prompted to add the instance to the trusted hosts, type yes to continue, you are now connected to the instance via SSH.

Installing Required Software

Once you connect to the instance through SSH, you will need to install some software that is required to run your application.

1- Install Curl: curl is a tool used to transform data to and from the Ubuntu server, in most cases it will be installed by default, so you might not need to install it explicitly.

sudo apt install curl

2- Install NodeJS: You have to install Node JS which comes bundled with the npm.

curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
sudo apt-get install -y nodejs

once installation is done you can verify the installation by running the command node --version . If you got something list v10.13.0 then the installation is done successfully.

3- (Optional) Install Yarn: Yarn is another package manager that some developers prefere to use over npm, if you want to install yarn, run the following commands:

curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn

4- Install PM2: PM2 is a production runtime and process manager for Node.js applications, we will use it instead of using npm start to run our application:

sudo npm install pm2@latest -g

Copying the files

At this stage our instance is ready to host the application, and we need to copy our application files in order to launch it.

Make sure to build your React application before you proceed further, in the case of the provided sample application, the build command is npm run build this will generate the static files that need to be served to the world.

the easiest way to copy files from your local machine to the remote instance is by using rsync. There are other ways of doing this, like using git to pull the files from a remote repository, we will tackle this in future articles when we talk about CI/CD pipelines, but in this tutorial we will use rsync.

1- Create the directories that will host the files, create a new folder on /var/www/myapp then create 2 subfolders:

mkdir /var/www/myapp/server
mkdir /var/www/myapp/client

2- Set permissions on those folders to enable the upload of files:

find /var/www/myapp/server -type d -exec sudo chmod 777 {} \;
find /var/www/myapp/client -type d -exec sudo chmod 777 {} \;

Important Note: Once you are done uploading the files, you have to change the permission to secure the application folders.

3- Use rsync to copy the file to the remote instance: We are assuming that the local files are on the ~/Projects/MyApp folder, you can change the local path according to the location of your files, and also change the instance name to the actual public DNS.

rsync -ravze "ssh -i ~/.ssh/MyInstance.pem " --exclude '.git/' --exclude 'node_modules/' ~/Projects/MyApp/client/*  ubuntu@ec2-1-1-1-1.us-east-2.compute.amazonaws.com:/var/www/myapp/client
rsync -ravze "ssh -i ~/.ssh/MyInstance.pem " --exclude '.env'  --exclude '.git/' --exclude 'node_modules/' ~/Projects/MyApp/server/*  ubuntu@ec2-1-1-1-1.us-east-2.compute.amazonaws.com:/var/www/myapp/server

4- Once you are done copying the files, make sure to secure the folders by updating the permissions:

find /var/www/myapp/server -type d -exec sudo chmod 775 {} \;
find /var/www/myapp/client -type d -exec sudo chmod 775 {} \;

5- Set the permission on the files under the main folder:

find /var/www/myapp/server -type f -exec sudo chmod 644 {} \;
find /var/www/myapp/client -type f -exec sudo chmod 644 {} \;

At this point the files are copied to the remote instance.

Installing dependencies

We can use npm to install the dependencies for both the server and the client, by going to reach of the 2 folders and run the npm install command.

cd /var/www/myapp/server
npm install
cd /var/www/myapp/client
npm install

Installation should take few minutes on each folder, and it should not return any errors, if you faced any errors, you have to investigate the installation log to determine what went wrong.

You can verify that everything is working fine by going to the server folder and execute the command npm run start or whatever start script suitable for your application, if you have a different configuration.

PM2 Configuration file

Next step is to configure the configuration file of PM2 in order to tell PM2 what commands it needs to execute to run your application.

PM2 can be configured via YAML, JS or JSON files, here we will be creating a JS file, the file name should end with .config.js. We will create the file on the path /var/www/myapp

# go to the folder where we want the file to be created
cd /var/www/myapp
# create the file using the touch command
touch myapp.config.js
# open the file for editing in nano
nano ./myapp.config.js

This will open the nano editor and will load the empty file, copy the code bellow to the file then save the file and exit the nano editor.

module.exports = {
apps : [{
name : "myapp-api",
script : "./server/server.js",
watch : true,
env: {
"NODE_ENV": "development",
},
env_production : {
"NODE_ENV": "production"
}
},
{
name : "myapp-client",
script : "./client/server.js",
watch : true,
env: {
"NODE_ENV": "development",
},
env_production : {
"NODE_ENV": "production"
}
}]
}

What we are doing here is defining 2 applications (server and client) and setting the start script for each one of them. Remember to change the script value in both applications to your actual start script file.

Once you copy the code to the nano editor press Ctrl+X to exit, you will be prompted to save the file, press Y to save.

Starting the PM2 Processes

our instance is now ready, from withen the /var/www/myapp folder, type the following command:

pm2 start myapp.config.js

This will both the client and the server, you will get an output similar to the screenshot bellow to indicate the both the client and server are running.

The application is now running, and if you access the public DNS of the instance on the port where the client is configured (in the case of the provided sample app it is 3000), you will see your application up and running.

🎊 🎉 Your Application is Now Online 🎊 🎉

But do not celebrate just yet, there is one small problem. If the instance was restarted for any reason, you will have to login to the SSH again and run the pm2 start command, you have to do this every-time the instance is restarted, which is not the best solution in the world.

In order to solve this, we will configure pm2 to start those processes automatically, while the instances are running, run the command pm2 startup. This will give you a command that you need to run in order to configure the startup script of pm2. Example output:

Copy the command and run it, and you are all set. You have created an instance on EC2, deployed you React application on it along with its associated back end and configured it to run automatically.

Next Steps

Now that your application is up and running, if you do any change to the back-end NodeJS or the front-end React application, you have to repeat some of the steps mentioned here in order to deploy those changes online. This can be a tedious job, but don’t worry, there is a solution for that, and we will discuss it in future articles when we talk about CI/CD pipelines.

If you found this article useful to you, please give it some claps so that it reaches others who can benefit from it as well. Please feel free to share your thoughts in the comments.