Skip to content

Latest commit

 

History

History
967 lines (587 loc) · 21.5 KB

project-3.md

File metadata and controls

967 lines (587 loc) · 21.5 KB

SIMPLE TO-DO APPLICATION ON MERN STACK

In this project, we are going to deploy a simple to-do application on the MERN STACK in AWS CLOUD.

MERN consists of the following:

  • MongoDB : This is a document based, no-SQL database used to store application data in form of document.

  • ExpressJs : This is a server side web application framework for Node.js.

  • ReactJs : This is a frontend framework used to build User Interface (UI) which is based on Javascript.This was developed by Facebook.

  • NodeJs : This is a JavaScript runtime environment. It is used to run JavaScript on a machine rather than in a browser.

To continue with this Project, we need to do the following:

  • create an account on AWS.
  • we create an instance (virtual machine) by selecting “ubuntu server 20.04 LTS” from Amazon Machine Image(AMI)(free tier).
  • we select “t2.micro(free tier eligible)”
  • then go to the security group and select “existing security group” review and launch.

How to create an aws free tier account. click here

This launches us into the instance as shown in the screenshot:

Image

We open our terminal and go to the location of the previously downloaded PEM file:

Image

To know how to download PEM File from AWS. Click here.

We connect to the instance from our ubuntu terminal using the command:

$ ssh -i dybran-ec2.pem [email protected]

This automatically connects to the instance

Image

When we are done connecting to the instance, we can proceed with setting up our project.

BACKEND CONFIGURATION

We run the commands:

To update Ubuntu.

$ sudo apt update

To upgrade Ubuntu

$ sudo apt upgrade

Next, we get the location of the Node.js software from the ubuntu repository by running this command:

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

After doing the above, we can now install the Node.js and npm by running the command:

$ sudo apt-get install -y nodejs

Image

To check the version the node:

$ node -v

Image

To check the version fo the npm:

$ npm -v

Next we create a directory for our To-Do Project:

$ mkdir Todo

Image

We run the ls command to verify that the directory was created:

$ ls

Change into the newly created directory:

$ cd Todo

Next, we initialize the project so that a new file package.json will be created. This file contains information of the application and the dependencies it nedds to run.

We initialize the project by running the command:

$ npm init

Image

We run the ls command to verify that package.json was created

$ ls

Next we install ExpresJs and create the Routes directory.

INSTALL EXPRESSJS

Express is a framework for NodeJs Express helps to define routes of your application based on HTTP methods and URLs.

To use Express, we install it using npm:

$ npm install express

Image

Now create a file index.js using the command:

$ touch index.js

We run ls to confirm that the index.js file is successfully created.

Next, install the dotenv module by running the command:

$ npm install dotenv

Image

Open the index.js file by running the command:

$ vim index.js

We copy the folowing into it and "save" using ":wq"

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

We start our server to see if it works. Before we start the server, we need to be sure we are in the Todo directory.

$ node index.js

If there are no erros, we should see ""Server running on port 5000" in your terminal.

Image

We need to go to our instance security group and edit inbound rules and add rules

Image

We open our browser and put in the URL

http://<PublicIP>:5000

We should see "welcome to Express"

Image

Our To-Do application should be able to do the following:

  • create a new task.
  • dislay the lists of tasks.
  • delcte acompleted task.

We need to create routes that will define various endpoints that the To-do application will depend on.

To create a folder routes, we run the command:

$ mkdir routes

Change into the routes directory

$ cd routes

Now, create a file api.js with the command:

$ touch api.js

Open the file

$ vim api.js

We then copy and paste the code below into the api.js file

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;

"save" and "quit" using ":wq"

CREATING MODELS

We then go ahead to create "Models" since the application will be using MongoDB which is a noSQL database. A Model makes the javascript application interactive. We also use Models to define the database schema . This is important so that we will be able to define the fields stored in each Mongodb document.

The Schema shows how the database will be setup, including other data fields that may not be required to be stored in the database.

To create a Schema and a model, we will need to install mongoose which is a Node.js package that makes working with mongodb easier.

To install Mongoose, we make sure we are in the Todo directory then run the command:

$ npm install mongoose

Image

We go ahead to create a folder models by running the command:

$ mkdir models

Then we chaneg into the model directory:

$ cd models

Inside the models folder, create a file named todo.js

$ touch todo.js

Image

We then open the file and paste the following codes:

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;

Image

We then go to our routes directory and update the api.js file to be able to make use of the new model.

$ cd routes

Open the api.js file

$ vim api.js

Image

We copy and paste the following codes into the file:

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;

Images

Next we setup the MongoDB Database.

SETTING UP THE MONGODB DATABASE

We use MongoDB Database to store our data using mLab which provides Database as a service (DBaas) solution.

To continue, we sign up here.

Follow the sign up process, select AWS as the cloud provider, and choose a region near you.

Image

Go to "Network access", select "Allow access from anywhere". This is not secure but good for testing purposes.

Image

Then change the time of deleting the entry from 6 Hours to 1 Week.

Image

Create a MongoDB database and collection inside mLab by clicking on "database", click on "cluster0" and then open "collections".

Image

In the index.js file, we specified process.env to access environment variables, but we are yet to create this file.

To create the process.env file, we creta a file in Todo directory and name it .env.

$ touch .env

Open the file

$ vim .env

Add the connection string to access the database in it, just as below:

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

Make sure to update "<username, <password, <network-address" and <database according to your setup.

To get the connection string, we click on "cluster0" then click on "connect"

Image

Then we click on "Mongodb drivers-connect your application..."

Image

We then copy then connection string displayed into the .env file.

Image-1a
Image-1b

We need to update the index.js to reflect the use of .env so that Node.js can connect to the database.

To do that we open the index.js file and delete the content using "esc" then ":%d" then "enter".

We then replace then content with the following codes:

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

Image

It is more secure to use environment variables to store information so as to separate configuration and secret data from the application, instead of writing connection strings directly inside the index.js application file.

We start our server by running the command:

$ node index.js

If the setup has no errors, we should see " Database connected successfully".

Image

We now have our backend configured, but we need to test it.

To test the backend without the frontend we are going to be using Restful API with the help of an API development client Postman.

To download and install Postman, click here.

Click here to learn how to perform CRUD Operations on Postman.

We test all the API endpoints and make sure they are working. For the endpoints that require body, you should send JSON back with the necessary fields since it’s what we setup in our code.

Now open your Postman, create a POST request to the API

http://<PublicIP>:5000/api/todos.

This request sends a new task to our To-Do list so the application could store it in the database.

Make sure to set the header "content-type" and "application/json":

Image

Then click on body. In the field below we key in a command that displays as a response on then next field with an id.

Image

Create a GET request to your API on

http://<PublicIP>:5000/api/todos.

This request retrieves all existing records from out To-do application. The backend requests these records from the database and sends it us back as a response to GET request.

Image

To delete a task – you need to send its ID as a part of DELETE request.

By now you have tested backend part of our To-Do application and have made sure that it supports all three operations we wanted:

  • Display a list of tasks – HTTP GET request
  • Add a new task to the list – HTTP POST request
  • Delete an existing task from the list – HTTP DELETE request

We have successfully created our Backend, now let go create the Frontend.

SETTING UP THE FRONTEND

We will create a user interface for a Web client (browser) to interact with the application via API.

To do this, we will start by running the command in the Todo directory:

$ npx create-react-app client

This will create a new folder in your Todo directory called client, where you will add all the react code.

Image

Running a React Application

There are some dependencies that need to be installed before running the React App.

We Install "concurrently" by running the command:

$ npm install concurrently --save-dev

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

Image

Next, we install "nodemon". This 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

Image

Goto the Todo directory, open the package.json file.

$ vim package.json

In this file, replace the "scripts" section with the following code:

"scripts": {
"start": "node index.js",
"start-watch": "nodemon index.js",
"dev": "concurrently \"npm run start-watch\" \"cd client && npm start\""
},

Image

Change into the client directory:

$ cd client

Open package.json file:

$ vim apckage.json

Add the key value pair in the package.json file

"proxy": "http://localhost:5000"

Image

The aim of adding the proxy to the file is to make 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.

We go back to the Todo directory:

$ cd ..

Then run the command:

$ npm run dev

The app should open and start running on localhost:3000.

In order to be able to access the application from the Internet you have to open TCP port 3000 on EC2 by adding a new Security Group rule. The same way the security group for TCP port 5000 was created. This is done by clicking edit inbound rules.

Images

Creating your React Components

One of the advantages of react is that it makes use of components, which are reusable and also makes code modular.

Our Todo app will have two stateful components and one stateless component.

Go to Todo directory run the command:

$ cd client

Change into the src directory

$ cd src

Inside your src folder create another folder called components

$ mkdir components

Move into the components directory:

$ cd components

Inside components directory create three files Input.js, ListTodo.js and Todo.js.

$ touch Input.js ListTodo.js Todo.js

This creates the three files at the same time.

Open Input.js file

$ vim Input.js

Copy and paste the following:

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

export default Input

Image

To make use of Axios, which is a Promise based HTTP client for the browser and node.js.

We go into the client directory and run the command:

$ cd ../..

Then run the command:

$ npm install axios

Image

We go to ‘components directory:

$ cd src/components

After that we open the ListTodo.js

$ vim ListTodo.js

In the ListTodo.js copy and paste the following codes:

import React from 'react';

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

Image

Open the Todo.js file we copy and paste the following code:

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

}
}

export default Todo;

We then move into the src directory:

$ cd ..

Open App.js file:

$ vim App.js

Copy and paste the following codes into the file:

import React from 'react';

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

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

export default App;

We Exit.

Image

Next we open the App.css file in the src directory and paste the following codes:

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

Image

Still in the same src directory, we open the index.css file:

$ vim index.css

We then copy and paste the following codes:

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 go to the Todo directory:

$ cd ../..

In the Todo directory, we then run the command:

$ npm run dev

If there are no errors, there should be a message "webfiles compiled successfully".

Image

To view this on the browser, we past the URL:

http://<PublicIP>:3000

Image

Our To-do application is ready and functional with the functionality discussed earlier: creating a task, deleting a task and viewing all your tasks.