How to deploy 3-tier Mern Application on AWS EC2 Instance
In this article, we want to deploy a todo app where the languages are reacts and nodejs, to production on our AWS ec2 instance, we would also use MongoDB as our database. At the end of this article, we should be able to implement the mern application on the ec2 instance. Let's dive in
We will start with the backend configuration which is nodejs
We will spin up an instance on your AWS console. We can create an AWS account, sign up here. Our instance should look like this
We can give your instance any name you like.
We need to connect to our instance either via ssh or via the AWS instance console
Update and upgrade your ubuntu by typing
sudo apt update && Sudo apt upgrade -y
Next is to install the latest version of nodejs
curl -fsSL
https://deb.nodesource.com/setup_18.x
| sudo -E bash -
sudo apt-get install -y nodejs
The commands above automatically installed npm with the node for us. We need to check the version of the node installed
node -v
npm -v
Since we want to set up a todo application. Let us create a folder called todo, this is also called making a directory
sudo mkdir Todo
Next is to cd into the todo directory that we created
cd Todo
The next thing we will do is to initialize npm inside this directory. This will help us create pakage.json. The package.json file contains information about the application and the dependencies that will help the application run.
npm init
We will keep pressing the enter key to take you to the next step. At keywords, we will type "A mern app" or "A todo app". The author can be your name or any name that we desire.
The next thing is to install an express js. We are still inside our Todo directory, express js helps us to define the routes of our application based on HTTP methods and URLs.
We will use npm (package manager) to install express
npm install express
We will create an index.js file
touch index.js
In case we get permission denied. We will type sudo in front of the touch
Sudo touch index.js
We will install dotenv using npm
npm install dotenv
Open the index.js file
sudo nano index.js
Paste the below code inside the index.js file
const express = require('express'); require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); });
app.use((req, res, next) => { res.send('Welcome to Express'); });
app.listen(port, () => { console.log(Server running on port ${port}) });
Save and close the file
We have told the app to listen on port 5000. We will start the server on port 5000 by typing the below code.
node index.js
We will allow port 5000 in our inbound rule on the AWS ec2 instance security group. This is a link on how to edit the inbound rule
Paste the public ip address on the browser. We will :5000 at the end of the ip address.
We need to create a route for the app to depend on
We will make route directory
mkdir routes
We will cd into the routes directory
cd routes
We will create a file called api.js
touch api.js
We will open the api.js file and paste the below code inside it
const express = require ('express'); const router = express.Router();
router.get('/todos', (req, res, next) => {
});
router.post
('/todos', (req, res, next) => {
});
router.delete('/todos/:id', (req, res, next) => {
})
module.exports = router;
We will move to create models since our database will be MongoDB. We need to because MongoDB is a NoSQL database.
First, we need to cd back into the Todo directory. Afterward, we will install mongoose using npm package manager.
npm install mongoose
Create a directory called models and cd into the directory
mkdir models && cd models
We will create a file called todo.js
touch todo.js
Paste the below code in the todo.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const TodoSchema = new Schema({
action: {
type: String,
required: [true, 'The todo text field is required']
}
})
const Todo = mongoose.model('todo', TodoSchema);
module.exports = Todo;
save and close the file
We will cd back into the route directory. This time, we will update our api.js file. We will delete the previous content inside the api.js and update it with the code below
const express = require ('express');
const router = express.Router();
const Todo = require('../models/todo');
router.get('/todos', (req, res, next) => {
Todo.find({}, 'action')
.then(data => res.json(data))
.catch(next)
});
router.post
('/todos', (req, res, next) => {
if(req.body.action){
Todo.create(req.body)
.then(data => res.json(data))
.catch(next)
}else { res.json({
error: "The input field is empty"
})
}
});
router.delete('/todos/:id', (req, res, next) => {
Todo.findOneAndDelete({"_id":
req.params.id
})
.then(data => res.json(data)) .catch(next) })
module.exports = router;
save and close the file.
Now it is time to create our database. Create an account with MongoDB here in case you do not have an account.
We will create a cluster0. Let us keep the password in other not to forget it for later use
We will allow access from anywhere
We will click on the collection, and click on "add my own data"
We will go back to our terminal. We will go back to our Todo directory. We will create a .env file, this file will help us to connect our app with our database.
touch .env
sudo nano .env
We will click on connect on the app MongoDB atlas dashboard and click on connect to the application. We will copy the code and paste. The code format is below
DB = 'mongodb+srv://<username>:<password>@<network-address>/<dbname>?retryWrites=true&w=majority'
save and close the file
We need to update our index.js in the Todo directory. This will make the todo app connect to the MongoDB database
sudo nano index.js
Paste the below code inside the file
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const routes = require('./routes/api');
const path = require('path'); require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
mongoose.connect(process.env.DB, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log(Database connected successfully)) .catch(err => console.log(err));
mongoose.Promise = global.Promise;
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); });
app.use(bodyParser.json());
app.use('/api', routes);
app.use((err, req, res, next) => {
console.log(err);
next(); });
app.listen(port, () => {
console.log(Server running on port ${port})
});
save and close the file
We will check if the database is connected by typing the below code
node index.js
we should get "
database successfully connected
" as a responseWe will use postman to test our backend API. This will be done by getting the postman app on our device here. After install our postman app on our device. We will navigate the header
We will make the key, content-type and value, application/json.
Then post a message that will be sent to the backend. Send the post created.
We will check our browser to know if the API has received our post. This will be done by pasting the below on our browser
ipaddress:5000/api/todos
We are done with the backend configuration. We will move to the front-end configuration. This will help our app have a good interface that people can relate to. We will use create react app to create the frontend
In our Todo directory, we will create a react app by typing the below code
npx create-react-app client
We need to install concurrently, this will help us run more than one command at the same time
npm install concurrently --save-dev
We also need to install nodemon. This will help us monitor our app
npm install nodemon --save-dev
We will change little things in our package.json in this directory. We will update the script content in this file. This will be done by deleting the script part and replacing it with the code below.
"scripts": {
"start": "node index.js",
"start-watch": "nodemon index.js",
"dev": "concurrently "npm run start-watch" "cd client && npm start""
},
We will cd into the frontend which we named client. We will add our frontend proxy port to the package.json file. This will make it easy for us to stop using the long /api/todos
"proxy": "http://localhost:5000"
Since react listens on port 3000. We need to add port 3000 to our inbound rule just like we added port 5000.
Then we will start our app by running
npm run dev
Our app should be listening on port 3000.
We need to make the interface look good. This will prompt us to create some files that will contain our interface code. From our client directory, we will cd into src, we will create a folder called component in our src directory.
mkdir components
We will touch 3 files inside the components directory.
touch Todo.js, Input.js, ListTodo.js
We will edit our Input.js file and paste the below code inside it.
import React, { Component } from 'react';
import axios from 'axios';
class Input extends Component {
state = { action: "" }
addTodo = () => {
const task = {action: this.state.action}
if(task.action && task.action.length > 0){
axios.post
('/api/todos', task) .then(res => {
if(
res.data
){ this.props.getTodos(); this.setState({action: ""})
}
})
.catch(err => console.log(err)) }else {
console.log('input field required') }
}
handleChange = (e) => { this.setState({ action:
e.target
.value }) }
render() { let { action } = this.state; return (
add todo
export default Input
save and close the file
We will cd back into our client directory and install axios
npm install axios
We will cd into the components directory, we need to edit some files. We will paste the below code inside ListTodo.js
import React from 'react';
const ListTodo = ({ todos, deleteTodo }) => {
return (
{ todos && todos.length > 0 ? (
todos.map
(todo => { return (
deleteTodo(todo._id)}>{todo.action}
) }) ) : (
No todo(s) left
) }
export default ListTodo
Then we will also paste the below code inside Todo.js
import React, {Component} from 'react';
import axios from 'axios';
import Input from './Input';
import ListTodo from './ListTodo';
class Todo extends Component {
state = { todos: [] }
componentDidMount(){ this.getTodos(); }
getTodos = () => { axios.get('/api/todos') .then(res => { if(
res.data
){ this.setState({ todos:
res.data
}) } }) .catch(err => console.log(err)) }
deleteTodo = (id) => {
axios.delete(/api/todos/${id}) .then(res => { if(
res.data
){ this.getTodos() } }) .catch(err => console.log(err))
}
render() { let { todos } = this.state;
return(
My Todo(s)
} }
export default Todo;
We will cd into src. We will update App.js to the lastest code below
import React from 'react';
import Todo from './components/Todo';
import './App.css';
const App = () => { return (
export default App;
We will also update our App.css with the below code.
.App {
text-align: center;
font-size: calc(10px + 2vmin);
width: 60%;
margin-left: auto;
margin-right: auto;
}
input {
height: 40px;
width: 50%;
border: none;
border-bottom: 2px #101113 solid;
background: none; font-size: 1.5rem; color: #787a80; }
input:focus { outline: none; }
button {
width: 25%;
height: 45px;
border: none;
margin-left: 10px;
font-size: 25px;
background: #101113;
border-radius: 5px;
color: #787a80;
cursor: pointer; }
button:focus { outline: none; }
ul {
list-style: none;
text-align: left;
padding: 15px;
background: #171a1f;
border-radius: 5px;
}
li {
padding: 15px;
font-size: 1.5rem;
margin-bottom: 15px;
background: #282c34;
border-radius: 5px;
overflow-wrap: break-word;
cursor: pointer;
}
@media only screen and (min-width: 300px) {
.App {
width: 80%;
}
input {
width: 100%;
}
button {
width: 100%;
margin-top: 15px;
margin-left: 0; } }
@media only screen and (min-width: 640px) { .App { width: 60%; }
input {
width: 50%;
}
button {
width: 30%;
margin-left: 10px;
margin-top: 0;
}
We will cd back into our src folder. We want to update the index.css folder inside it, we will paste the below code inside index.css
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
box-sizing: border-box; background-color: #282c34; color: #787a80; }
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; }
We will go back to the Todo directory to finally start our functional app by running
npm run dev
Thank you for coming this long with me. I hope we have been able to deploy a mern app together.
Refrences
https://www.darey.io/docs/simple-to-do-application-on-mern-web-stack/
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html
https://adhasmana.medium.com/how-to-deploy-react-and-node-app-on-aws-a-better-approach-5b22e2ed2da2
Subscribe to my newsletter
Read articles from Adelakin Adewumi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adelakin Adewumi
Adelakin Adewumi
I'm a Cloud/DevOps engineer and a technical writer. I will be a world-class Cloud Architect. Aside from tech, I love traveling, meeting new people, and reading novels. I am a smart and intelligent lady. I am jovial and happy to be with.