3.PROJECT3-MERN-STACK

IMPLIMENTING A WEB SOLUTION BASED ON MERN STACK ON AWS WEB SERVER

MERN

MERN stack is a web development framework. It consists of MongoDB, ExpressJS, ReactJS, and NodeJS as its working components. These components are used in developing a web application when using MERN stack as follows:

MongoDB: A document-oriented, No-SQL database used to store the application data.

NodeJS: The JavaScript runtime environment. It is used to run JavaScript on a machine rather than in a browser.

ExpressJS: A framework layered on top of NodeJS, used to build the backend of a site using NodeJS functions and structures.

ReactJS: A library created by Facebook. It is used to build UI components that create the user interface of the single page web application

Creating an Ec2 instance

We start the project by creating an ec2 instance on aws server and saving the keypair with type .pem

Screenshot from 2023-02-24 04-48-40

After this, on your windows terminal, cd into the directory containing your .pem file and connect to the instance by running:

ssh -i <private_keyfile.pem> username@ip-address

BACKEND CONFIGURATION

First, we run the commands sudo apt update and sudo apt upgrade

apt update

Next, we find the location of node.js in ubuntu repositories with this command

curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

nodecurl

Now, we use sudo apt-get install -y nodejs command to install node.js on our machine. With this, we will install both node and npm which is a package manager for nodejs used to install other modules and manage dependency conflict

nodeinstall

Run node --v to verify node was installed and npm --v to verify npm was installed

node and npmv

Setting up Application code

First, we create a Todo directory where all packages of our application would be contained.

mkdir Todo

Next, we change directory into the Todo directory created with cd Todo command

After this, we run the npm init command. This enables a package.json file to be created which contains files that would enable our application and its dependencies to run.

todo init

Install Expressjs

Next, we install the javascript express js which we will help us in URL routing and HTTP request making in our application

Run npm install express to install expressjs

installexpress

Create index.js file by running

touch index.js

Next, we install the dotenv module, this a package that helps keep passwords, API keys, and other sensitive data out of code. It allows us to create environment variables in a .env file instead of putting them in our code.

npm install dotenv

dotenv

Open index.js file using the command

vim index.js

copy and paste the in index.js with the following code:

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}`)
});

Use esc, :wq to save and exit vim

Next, we test if the server is working correctly by running the command node index.js

node500confirm

The image shows that our server is running on port 5000 and we need to open this port 5000 in our ec2 instance security group settings inbound rule

tcp5000

Now need to access the server through our public ip address or DNS with this command

http://<PublicIP-or-PublicDNS>:5000

welcexpressip

The image above shows we can access the server. Done

Creating Routes

Our Todo app is expected to be able to create

  1. Create new task
  2. Get list of all tasks
  3. Delete completed tasks

For each of these, we need to create an endpoint and use standard HTTP request methods: POST; GET; DELETE

we need to create routes that will define various endpoints that the To-do app will depend on for each task. Let us create routes:

mkdir routes

change directory to routes

cd routes

Create api.js file with

touch api.js

open the file with vim api.js

Copy and paste the following line of codes

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;

editapijs

Save and exit with esc, :wq and hit enter

Installing Models

Since we are using mongodb for this application, we create models for it. Models makes javascript based applications interactive.

To create this, we will install mongoose;

Change directory to the Todo directory with cd ..

Type in command npm install mongoose

mongoose

Next, create models directory with mkdir models

change directory into models with cd models and create a named todo.js inside with touch todo.js

Open file todo.js with vim todo.js and paste the following line of codes within:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

//create schema for todo
const TodoSchema = new Schema({
action: {
type: String,
required: [true, 'The todo text field is required']
}
})

//create model for todo
const Todo = mongoose.model('todo', TodoSchema);

module.exports = Todo;

todojsedit

Now we need to update our routes from the file api.js in ‘routes’ directory to make use of the new model.

In Routes directory, open api.js with vim api.js, delete the code inside with :%d command and paste the code below into it then save and exit

const express = require ('express');
const router = express.Router();
const Todo = require('../models/todo');

router.get('/todos', (req, res, next) => {

//this will return all the data, exposing only the id and action field to the client
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;

editapijs

INSTALL DATABASE MONGODB

We need a database for the storage of our data from the application and for this purpose, we will be using mongodb. Visist https://www.mongodb.com/atlas-signup-from-mlab to create an account. Select shared cluster and aws as the cloud provider, choose the closest region to you.

Login into mLab and create a cluster

mongodbreg

Create a database and a collection

dbcollection

Connect mongoose to our database service: We connect to it using the connect details from mongodb

Click on clusters, then click on connect

connstring

constring2

Copy the connection code and save it inside a .env file which should be created in the parent todo directory

DB = 'mongodb+srv://<username>:<password>@<network-address>/<dbname>?retryWrites=true&w=majority'

Change the username, password,database name and network address to the one specified by you when creating the database and collection.

Update the code in our index.js as we need to point mongoose to the database service we created using mLab

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;

//connect to the database
mongoose.connect(process.env.DB, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log(`Database connected successfully`))
.catch(err => console.log(err));

//since mongoose promise is depreciated, we overide it with node's promise
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}`)![postman](https://user-images.githubusercontent.com/77943759/221338746-4bfe1493-c5ff-46b1-bf01-26fd5de44a20.png)

});

Start the server by running command node index.js

connectnodejs

The message of connecting successfully above confirms our server runs perfectly.

Testing Backend Code using RESTful API

In this case, we will use Postman, an API development client to test our code since we dont have frontend UI yet

Visit https://www.postman.com/downloads/ to download and istall Postman

Open postman and create a POST request with the endpoint http://<PublicIP-or-PublicDNS>:5000/api/todos, in the header section: Select content-Type as key and application/json as value; in the body session: select raw and enter any message of your choice.

postrequest

Next, create a GET request with same endpoint http://<PublicIP-or-PublicDNS>:5000/api/todos

getrequest

Create a DELETE request

deleterequest

We have been able to:

Create task (POST request)

Display available tasks (GET request)

Remove/Delete completes tasks (DELETE request)

CREATING FRONTEND

We need to create a user interface for clients to interact with the server

We will use create-react-app client command to create a new directory in our Todo directory

Run npx create-react-app client in the Todo directory

We need to install the following dependencies before testing our react app

concurrently: It is used to run more than one command simultaneously from the same terminal window.

npm install concurrently --save-dev

nodemon: It is used to run and monitor the server. If there is any change in the server code, nodemon will restart it automatically and load the new changes.

npm install nodemon --save-dev

Go to Todo directory,  open the package.json file. Change the highlighted part of the below screenshot and replace with the code below.
 "scripts": {
"start": "node index.js",
"start-watch": "nodemon index.js",
"dev": "concurrently \"npm run start-watch\" \"cd client && npm start\""
},

wheretochange

Configure Proxy in package.json

Change directory to client cd client

Open package.json file vi package.json

Add "proxy": "http://localhost:5000" keypair to the file. This makes it possible to access the application directly from the browser by simply calling the server url like http://localhost:5000 rather than always including the entire path like http://localhost:5000/api/todos.

Go to Todo directory and run npm run dev

npmrundev

Our server is now running on port 3000.

We need to open this port in our ec2 instance inbound rule setting

tcp3000

Creating your React Components

React makes use of components, this makes codes modular and the components are reusable. We will create 2 stateful and 1 stateless component for our Todo app

cd client

cd src

We will create a directory for components in src

mkdir components

We will create these three new files in components directory: Input.js, ListTodo.js, and Todo.js

touch Input.js ListTodo.js Todo.js

open Input.js file with vi Input.js and paste the code below inside

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 (
<div>
<input type="text" onChange={this.handleChange} value={action} />
<button onClick={this.addTodo}>add todo</button>
</div>
)
}
}

export default Input

To make use of Axios, which is a Promise based HTTP client for the browser and node.js, we need to cd into into client from the terminal and run npm install axios

cd ..

cd ..

Run:

npm install axios

axios

Go back to the component directory cd src/components

Open ListTodo file vi ListTodo.js and paste the code below inside


const ListTodo = ({ todos, deleteTodo }) => {

return (
<ul>
{
todos &&
todos.length > 0 ?
(
todos.map(todo => {
return (
<li key={todo._id} onClick={() => deleteTodo(todo._id)}>{todo.action}</li>
)
})
)
:
(
<li>No todo(s) left</li>
)
}
</ul>
)
}

export default ListTodo

ListTodo

Open Todo.js vi Todo.js and paste the following code inside

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(
      <div>
        <h1>My Todo(s)</h1>
        <Input getTodos={this.getTodos}/>
        <ListTodo todos={todos} deleteTodo={this.deleteTodo}/>
      </div>
    )

}
}

export default Todo;

Todojs

We need to make adjustment to our react code. Delete the logo and adjust our App.js.

Return to the src directory

cd ..

In the src folder, open App.js vi App.js and paste the code below


import Todo from './components/Todo';
import './App.css';

const App = () => {
return (
<div className="App">
<Todo />
</div>
);
}

export default App;

appjs

Save and exit.

In the src directory, open index.css file vim index.css

Copy and paste this:

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

indexcssedit

Return to Todo directory cd ../..

Run npm run dev

myapp

This page confirms we have successfully created a Todo app

END