Starter application using Node.js + React.js + Nginx + Docker, being deployed to AWS EC2 - Step to step
Tags: webdev, javascript, docker, aws
Published: 2024-02-24 17:20:43

## Summary

- [Introduction](#introduction)
- [Step 1 - Accessing the repository](#step-1-accessing-the-repository)
- [Step 2 - Understand the application](#step-2-understand-the-application)
- [Step 3 - Create a instance EC2 on AWS](#step-3-create-a-instance-ec2-on-aws)
- [Step 4 - Defining the instance security groups](#step-4-defining-the-instance-security-groups)
- [Step 5 - Connect to the instance through the terminal](#step-5-connect-to-the-instance-through-the-terminal)
- [Step 6 - Install docker on the instance](#step-6-install-docker-on-the-instance)
- [Step 7 - Clone application repository and configure it based on the instance IP](#step-7-clone-application-repository-and-configure-it-based-on-the-instance-ip)
- [Step 8 - Run application in docker](#step-8-run-application-in-docker)
- [Step 9 - Congratulations!](#step-9-congratulations)

## Introduction

I see on the internet many ways to develop web applications, with different technologies and languages combined with each other, but I rarely see an updated tutorial on how to build an application from the beginning to production, working on a pre-determined IP address, where it is possible to access the same through the browser.

Here I will cover a tutorial on how to carry out this task, where the application will be composed of **React.js** on the front-end, **Node.js** on the back-end, **Nginx** to take the application to production and **Docker** to containerize the application that, finally, running on an **EC2** instance on **AWS**.

---

## Step 1 - Accessing the repository

The first step will be to access the web application repository, what can accessed in this link: https://github.com/fco3lho/node-react-nginx-docker-starter

This application is very simple, containing only a POST route and a GET route. It is basically based on creating tasks and showing them on the screen.

---

## Step 2 - Understand the application

Let's understand the application.

### Front-end

In the **front-end** folder, there are all the React.js files, but the main one is the **src/App.js** file, and the **package.json** file is where the dependencies that will be used by **src/App.js**, such as **Axios**. Let's analyze:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3p4hgfnfh9vg2xlimz00.png)

- At the top of the **src/App()** function, we have the declaration of 3 variables that will be used by the next 2 functions.
- In the middle, we have the **handleSubmit()** function, which is the function that will send the task to the back-end, so that it can register it.
- Below, we have the hook **useEffect()**, which is executed whenever the application is started and also when the condition given to the hook is true, in this case, when the variable **condition** is true. The **useEffect()** has the function of updating the task list whenever a new item is inserted in it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rvul1w6fvowvj0780r3.png)

Here in the front-end HTML, there are two parts:
- The first part is the form for entering a task.
- In the second part, it is a **map** that prints all registered lists in order of ID.

### Back-end

In the **back-end** folder, there are all the Node.js files, but the main one is the **index.js** file, and in the **package.json** file are all the dependencies that will be used by **index.js**, such as **express** and **mysql2**. Let's analyze:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuvgx1ed3dyuvz0ays4x.png)

- At the top of the code, all the import of dependencies and their use are carried out.
- In the middle, the connection to the database is created, where the _host_ is defined as _mysql_, as it will be the name of our containerized service in the future, _user_ and _password_ defaults to _root_, and the database named as _tasks_.
- At the bottom, the connection is established, returning a success message when connecting or an error message for not connecting.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5d0v52mrdwy35trfpbg.png)

- At the top of the code, the GET request is made, which returns all the content present in the database to the front-end.

- In the middle, the POST request is made, which inserts a new task, coming from the front-end, into the database.

- At the bottom, is where the server is running, in this case, on port 3001.

### Database

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpjxa3xnipncpid8sluh.png)

- Schema for the database, creating just a table with task ID and description.

### Nginx

The Nginx folder contains the **default.conf** file, and it is present in the **front-end** folder, as we will need the **build** folder for **React** and **default.conf ** in a single place to be run together in the **Dockerfile**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7s6scbzmvde7sc7b4yos.png)

- At the top, we have declared which port Nginx will listen on.
- In the middle, we have control of the _URL/_, which will be served to the front-end, along with the CORS configuration to accept requests from all sources.
- At the bottom, we have control of the _URL/api_, which will be the route that the front-end will use to communicate with the back-end, along with the _proxy_pass_ that defines the back-end route. In conclusion, this part of the code basically has control of _proxy_pass/api_.

### Dockerfile front-end

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xzzazzznddz503upeho2.png)

- In line 2, the Node image is used and named _builder_ for Docker.
- In lines 4 and 5, the folder with the directory _/usr/src/app_ is created and defined as the working directory.
- In lines 7 and 8, the _package.json_ file is copied from the local machine to the container's working directory, and then the _npm install_ command is executed.
- On line 10, the rest of the files from the local machine are copied to the container's working directory.
- On line 12, the _npm run build_ command is executed to create a production-optimized version of the application's front-end.
- In line 15, the Nginx image is used.
- On line 17, port 80 is exposed.
- On line 19, the file _./nginx/default.conf_ from the local machine is copied to the Nginx configuration folder, _/etc/nginx/conf.d/default.conf_, in the container's working directory.
- On line 20, the builder's _build_ folder, named on line 2, is copied to the Nginx application folder, _/usr/share/nginx/html_.

### Dockerfile back-end

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8csstx48jnef6pezr8hc.png)

- The same logic as the front-end Dockerfile is followed, however on line 11 the global installation of _pm2_ is performed, on line 13 port 3001 is exposed, and on line 15 the _pm2_ command is used to run the back-end.

### docker-compose.yml

This is the file that executes the other Dockerfiles and the most important part of it is the creation of the MySQL container and the container dependencies, since the rest of the front-end and back-end logic are in the Dockerfiles.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yd4oxccte9eiqboyr7ve.png)

- In _environment_ in the _mysql_ container, the database password and the database are declared.
- In _volumes_ in the _mysql_ container, the database schema that we will need is copied to the MySQL database initialization directory in the container, which creates our task database.
- In _depends_on_ on the _frontend_ and _backend_ containers, they are what the containers depend on to function, so the _backend_ depends on the health service of the _mysql_ container and the _frontend_ depends on the _backend_ to function.

---

## Step 3 - Create a instance EC2 on AWS

1. Log in to the AWS website by clicking <a href="http://console.aws.amazon.com/">here</a>.
2. Search for and sign in to the EC2 service by clicking <a href="https://sa-east-1.console.aws.amazon.com/ec2/home?region=sa-east-1#Home:"> here</a>
3. Click _'Launch Instance'_ and create an instance with:
  - Operating System: Ubuntu 22.04;
  - Architecture: 64 bits;
  - Instance type: t2.micro (Free Tier)
  - Create a new pair of RSA keys and .pem extension. When creating the new key pair, it will be downloaded, which you must keep to be able to connect to the instance.
4. The instance creation summary should look like this:![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89ui632fxq7o22t6t9cz.png)
5. Click _'Run instance'_.

Note: Take care not to be charged by AWS if you only want to use the Free Tier.

---

## Step 4 - Defining the instance security groups

1. Now go to the instances tab and click on the instance ID you created.
2. In the instance details, click on the _'Security'_ tab.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1k6xomqyzovta18zkx99.png)
3. Now click on the security group, which in my case is named _'sg-03e9813c42229fff2 (launch-wizard-1)'_.
4. Click _'Edit entry rules'_.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oad19t6umeqmolde0uz6.png)
5. Save the following input rules:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r95jwew5ioalg61ix16g.png)



---

## Step 5 - Connect to the instance through the terminal

1. Return to the instances tab and click on your instance ID.
2. Click _'Connect'_.
3. The tutorial on how to connect to your instance will appear.

In my case, my key is in the _'Downloads'_ folder, so I will open the terminal there and execute the commands mentioned in the tutorial. That way:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lznb19iftbsbnay5b483.png)

Here we are already connected to the instance.

---

## Step 6 - Install docker on the instance

1. ```bash
sudo apt update
```
2. ```bash
sudo apt upgrade -y
```
3. ```bash
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
```
4. ```bash
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
5. ```bash
sudo apt update
```

The `docker` user group exists but contains no users, which is why you’re required to use `sudo` to run Docker commands. 

To create the `docker` group and add your user:

1. ```bash
sudo groupadd docker
```

2. ```bash
sudo usermod -aG docker $USER
```

3. ```bash
newgrp docker
```

---

## Step 7 - Clone application repository and configure it based on the instance IP

1. Clone the repository with the link https://github.com/fco3lho/node-react-nginx-docker-starter.git, using the following command:```bash
git clone https://github.com/fco3lho/node-react-nginx-docker-starter.git
```

2. Enter the project folder using the following command:```bash
cd node-react-nginx-docker-starter/
```

Now, let's copy the public IPv4 address of the instance, which can be found in the instance summary:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zv3v8m9vti3lnv9yvdn1.png)

Copy the address which in my case is 15.228.99.248.

Now, let's first configure Nginx, going into its folder and modifying the configuration file:

1. Use the following command to enter the Nginx configuration folder:```bash
cd frontend/nginx
```

2. Use the following command to modify the configuration file:```bash
nano default.conf
```

3. Change the _proxy_pass_ in the _location /api_ block from _localhost_ to the public IPv4 address of your instance. This way:![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bwe7oz38k4pb2l71oc1c.png)

4. Save using the command _CTRL + O_, _Enter_ to confirm and _CTRL + X_ to exit.

Now let's configure the address that the frontend sends its requests to in _App.js_.

1. Use o seguinte comando para ir para a pasta do _App.js_: ```bash
cd ../src/
```

2. Use the following command to modify _App.js_:```bash
nano App.js
```

3. Modify the two _Axios_ routes, removing the _localhost_ and placing the public IP address of the instance, like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rpgfyi7t3tgsukgf5etr.png)

4. Save using the command _CTRL + O_, _Enter_ to confirm and _CTRL + X_ to exit.

Now the application is configured to run on the EC2 instance. Let's execute it.

---

## Step 8 - Run application in docker

1. Go back to the project root folder with the following command: ```bash
cd ../../
```

2. Run the application with Docker, using the following command:```bash
docker compose up -d
```

---

## Step 9 - Congratulations!

Now you can access the application in your browser, just enter the public IPv4 address of your instance in the address bar.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4ewnyt3wbvujz96e450.png)

### Notes

- Be careful when creating or changing configurations in AWS, as this may incur some costs.
- As this is an instance with weak Free Tier configurations, it is sometimes necessary to reload the page for it to update the creation of a task.
- I apologize for possible English errors, I enjoyed the idea of creating this tutorial together with training my English.

If you've made it this far, follow me on Github and connect with me on LinkedIn:

- Github: https://github.com/fco3lho
- LinkedIn: https://www.linkedin.com/in/fco3lho/