How to deploy 3-tier Mern Application on AWS EC2 Instance

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

  1. 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.

  2. We need to connect to our instance either via ssh or via the AWS instance console

  3. Update and upgrade your ubuntu by typing

    sudo apt update && Sudo apt upgrade -y

  4. 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

  5. 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

  6. 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

  7. Next is to cd into the todo directory that we created

    cd Todo

  8. 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.

  9. We will use npm (package manager) to install express

    npm install express

  10. 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

  11. We will install dotenv using npm

    npm install dotenv

  12. Open the index.js file

    sudo nano index.js

  13. 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

  14. 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

  15. 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

  16. 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

  1. We will make route directory

    mkdir routes

  2. We will cd into the routes directory

    cd routes

  3. We will create a file called api.js

    touch api.js

  4. 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.

  1. First, we need to cd back into the Todo directory. Afterward, we will install mongoose using npm package manager.

    npm install mongoose

  2. Create a directory called models and cd into the directory

    mkdir models && cd models

  3. We will create a file called todo.js

    touch todo.js

  4. 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

  5. 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.

    1. We will create a cluster0. Let us keep the password in other not to forget it for later use

    2. We will allow access from anywhere

    3. 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.

  1. touch .env

  2. sudo nano .env

  3. 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

  1. sudo nano index.js

  2. 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

  1. We will check if the database is connected by typing the below code

    node index.js

    we should get "database successfully connected" as a response

    We 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

    1. We will make the key, content-type and value, application/json.

    2. 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

  1. In our Todo directory, we will create a react app by typing the below code

    npx create-react-app client

  2. We need to install concurrently, this will help us run more than one command at the same time

    npm install concurrently --save-dev

  3. We also need to install nodemon. This will help us monitor our app

    npm install nodemon --save-dev

  4. 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""

    },

  5. 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"

  6. Since react listens on port 3000. We need to add port 3000 to our inbound rule just like we added port 5000.

  7. Then we will start our app by running

    npm run dev

  8. 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.

    1. mkdir components

    2. We will touch 3 files inside the components directory.

      touch Todo.js, Input.js, ListTodo.js

    3. 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

    4. We will cd back into our client directory and install axios

      npm install axios

    5. 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

    6. 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;

    7. 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;

    8. 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;

      }

    9. 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; }

    10. 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

https://mongobd.com

https://www.postman.com/downloads/