Sunday, May 3, 2020

MVC Architecture


" MVC is an acronym for Model-View-Controller "

It is a design pattern for software projects. It is used majorly by Node developers and by C#, Ruby, PHP framework users too. In MVC pattern, application and its development are divided into three interconnected parts. The advantage of this is it helps in focusing on a specific part of the application name, the ways information is presented to and accepted from, the user. It helps in allowing for efficient code reuse and the parallel development of the application. 
Model: Model represents the structure of data, the format, and the constraints with which it is stored. It maintains the data of the application. Essentially, it is the database part of the application.
View: View is what is presented to the user. Views utilize the Model and present data in a form in which the user wants. A user can also be allowed to make changes to the data presented to the user. They consist of static and dynamic pages that are rendered or sent to the user when the user requests them.
Controller: Controller controls the requests of the user and then generates an appropriate response which is fed to the viewer. Typically, the user interacts with the View, which in turn generates the appropriate request; this request will be handled by a controller. The controller renders the appropriate view with the model data as a response.
So, to sum it up:
·       Model is data part.
·       View is User Interface part.
·       Controller is request-response handler.

In short these are the steps:
  1. A Request is sent to the server and handled by the application controllers.
  2. Express.js and its routes can behave like controllers handling requests and determining whether to process the request further or send back a response.
  3. The routes may also trigger more code logic in other modules, communicate with database, or render a view.
  4. The client receives a response after the controller decides to generate views to send back. 
Implementation of MVC

Step-1: First, initialize npm in a directory of your choice
npm init
Then accept all the default values by pressing the enter key. It will create the package.json file.

Step-2: Now we will install the Express framework and the basic packages for our application.
npm i express body-parser mongoose express-handlebars --save
  • express: Fast, efficient, minimalist web framework for node.
  • body-parser: Node.js body parsing middleware.
  • express-handlebars: Handlebars view engine for Express.
  • mongoose: Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment.
After installing let’s have a look at the project setup.

Step-3: Project Setup- We have folders Model, View, Controller in our project, the directory structure is as follows:


Step-4: Create a database in MongoDB using the following steps:
  • Open Mongod Server
  • Open MongoDB Compass Community
  • Create database "studentDB" and a collection "students"

Step-5: Now create a folder in your application folder as "Models" and then create a file db.js for creating a connection with the MongoDB database.

db.js
const mongoose = require('mongoose');
mongoose.connect(
  ' mongodb://127.0.0.1:27017/studentDB',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },
  (err=> {
    if (!err) {
      console.log('MongoDB Connection Successful');
    } else {
      console.log('Error in Connection:' + err);
    }
  }
);

Step-6: Now create another file student.model.js in "Models" folder for creating a schema for the database, this will be used as a data-structure for getting data from the client and save in the database.

student.model.js 
const mongoose = require('mongoose');
var studentSchema = new mongoose.Schema({
  fullName: {
    type: String,
  },
  email: {
    type: String,
  },
  mobile: {
    type: String,
  },
  city: {
    type: String,
  },
  registerOn: {
    type: Date,
    default: Date.now,
  },
});

mongoose.model('Student'studentSchema);

Step-7: Now create express app in file server.js that listen at Port 3000.

server.js 
// Importing Models in Express App
require('./models/db');
const express = require('express');

// App Port
const app = express();
app.listen(3000, () => console.log('App started at 3000'));

Step-8: Now create another folder in your application folder as "Controllers" and then create a file studenController.js for creating routes of the application. 

studenController.js 
const express = require('express');
const router = express.Router();
router.get('/', (reqres=> {
  res.send('sample Text');
});

module.exports = router;

Step-9: Update server.js by importing controller and router middleware

server.js 
// import controller
require('./models/db');
const studentController = require('./controller/studentController');
// Router Middleware
app.use('/student'studentController);

Output in Browser- http://localhost:3000/student   
Step-10: Now update server.js for Configuring Express Handlebars.
server.js 
// Import Express Handlerbar
const path = require('path');
const exphbs = require('express-handlebars');
// Configure View Engine
app.set('views'path.join(__dirname'/views/'));
app.engine(
  'hbs',
  exphbs({
    extname: 'hbs',
    defaultLayout: 'mainLayout',
    layoutsDir: __dirname + '/views/layouts/',
  })
);
app.set('view engine''hbs');

Step-11: For Views, create another folder in your application folder as "views" and then create folder "layouts" insidethat store our main layout, Create a file mainLayout.hbs.
mainLayout.hbs
<!DOCTYPE html>
<html lang="en">
<head>
    <title>NodeJS Express MongoDB CRUD</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-secondary">
    <div class="row">
        <div class="col-md-8 offset-md-2" style="background-color: white; padding:20px; margin-top:25px;">
            {{!-- this inherit our view --}}
            {{{body}}}  
        </div>
    </div>
</body>
</html>

Step-12: For Views, create folder "student" in your "views" folder that stores our view. Create a file as addOrEdit.hbs handlebar.

addOrEdit.hbs 
<h3>{{viewTitle}}</h3>
<form>
    <div class="form-group">
        <label>Full Name</label>
        <input type="text" class="form-control" name="fullName" placeholder="Full Name">
        <div class="form-group">
            <label>Email</label>
            <input type="text" class="form-control" name="email" placeholder="Email">
            <div class="form-row">
                <div class="form-group col-md-6">
                    <label>Mobile</label>
                    <input type="text" class="form-control" name="mobile" placeholder="Mobile">
                </div>
                <div class="form-group col-md-6">
                    <label>City</label>
                    <input type="text" class="form-control" name="city" placeholder="City">
                </div>
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-info"><i class="fa fa-database"></i> Submit</button>
            </div>
</form>

Step-13: Now update studentController.js for rendering the above view.
studentController.js 
const express = require('express');
const router = express.Router();
// render View 
router.get('/', (reqres=> {
  res.render('student/addOrEdit', {
    viewTitle: 'Insert New Student',
  });
});
Output in Browser- http://localhost:3000/student   

Step-14: Now update the following changes in addOrEdit.hbs form Handlebars, server.js and in studentController.js for posting data.

addOrEdit.hbs

<form method="POST" action="/student" autocomplete="off">

server.js 

// Import body-parser package 
const bodyparser = require('body-parser');
app.use(
  bodyparser.urlencoded({
    extended: true,
  })
);
app.use(bodyparser.json());


studentController.js 

router.post('/', (reqres=> {

  console.log(req.body);
});

Output in Nodemone server: This means data is Posting

[nodemon] starting `node server.js` App started at 3000 MongoDB Connection Successful { fullName: 'Pankaj kapoor', email: 'pankkap@gmail.com', mobile: '9729280731', city: 'Chandigarh' }


Step-15: Now update in studentController.js for posting data into the MongoDb database. 

studentController.js

const mongoose = require('mongoose');
require('./../models/student.model');
const Student = mongoose.model('Student');

router.post('/', (reqres=> {
  insertRecord(reqres);
});

function insertRecord(reqres) {
  var student = new Student();
  student.fullName = req.body.fullName;
  student.email = req.body.email;
  student.mobile = req.body.mobile;
  student.city = req.body.city;
  student.save((errdocs=> {
    if (!err) {
      res.redirect('student/list');
    } else {
      console.log('Error during Record Insertion...' + err);
    }
  });
}
router.get('/list', (reqres=> {
  res.json('Student List');
});

Output in Browser- http://localhost:3000/student : Insert your student Data in the Form and press Submit Button, You will be redirected to /student/list display the sample ouput as Student List (This view will be update in the next session). At the same time, data will also be stored in the MongoDB. 

 


Step-16: Now update in studentController.js for reading the list of student's records from MongoDb database
and display in /student/list router. 

studentController.js

router.get('/list', (reqres=> {
  Student.find((errdocs=> {
    if (!err) {
      res.render('student/list', {
        list: docs,
      });
    } else {
      console.log('Error in retrieving employee list :' + err);
    }
  });
});

Step-17: Now we have to create a new express handlebar that will display the list of students' records. 

list.hbs

<h3>Student List</h3>
<table class="table table-striped">
    <thead>
        <tr>
            <th>Full Name</th>
            <th>Email</th>
            <th>Mobile</th>
            <th>City</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        {{#each list}}
        <tr>
            <td>{{this.fullName}}</td>
            <td>{{this.email}}</td>
            <td>{{this.mobile}}</td>
            <td>{{this.city}}</td>
            <td>
                <a><i class="fa fa-pencil fa-lg " aria-hidden="true"></i></a>
                <a><i class="fa fa-trash fa-lg" aria-hidden="true"></i></a>
            </td>
        </tr>
        {{/each}}
    </tbody>
</table>


Step-18: Now we add two buttons in both views one for creating New Student and 2nd View List of students. 
list.hbs

<h3><a class="btn btn-secondary" href="/student"><i class="fa fa-plus">
</i> Create New</a> Student List</h3>

addOrEdit.hbs 

<a class="btn btn-secondary" href="/student/list"><i class="fa fa-list-alt">
</i> View All</a>


Step-19: When using express Handlebar, there is an error in retrieving data from the database.

[nodemon] starting `node server.js`
App started at 3000
MongoDB Connection Successful
Handlebars: Access has been denied to resolve the property "fullName" because it 
is not an "own property" of its parent.

This issue can be resolved by adding the following code in server.js file.

const Handlebars = require('handlebars');
const {
  allowInsecurePrototypeAccess,
} = require('@handlebars/allow-prototype-access');

// Configure View Engine
app.set('views'path.join(__dirname'/views/'));
app.engine(
  'hbs',
  exphbs({
    extname: 'hbs',
    defaultLayout: 'mainLayout',
    layoutsDir: __dirname + '/views/layouts/',

    // Issue in Access has been denied to resolve the property
    //"---" because it is not an "own property" of its parent.
    handlebars: allowInsecurePrototypeAccess(Handlebars),
  })
);
app.set('view engine''hbs');

Output in Browser- http://localhost:3000/student/list   



Step-20: Now we create a new route in studentController.js for Edit Button to update the previously stored record.

studentController.js: This will open the view for update the previous record with ViewTitle proper: Update student

router.get('/:id', (reqres=> {
  Student.findById(req.params.id, (errdoc=> {
    if (!err) {
      res.render('student/addOrEdit', {
        viewTitle: 'Update Student',
        student: doc,
      });
    }
  });
});

Note: For step-20: Updating Record in the application following changes needs to be updated.

list.hbs

<td>
       <a href="/student/{{this._id}}"><i class="fa fa-pencil fa-lg "
          aria-hidden="true"></i></a>&nbsp;&nbsp;&nbsp;
       <a><i class="fa fa-trash fa-lg" aria-hidden="true"></i></a>
</td>

addOrEdit.hbs 
<h3>{{viewTitle}}</h3>
<form method="POST" action="/student" autocomplete="off">
    <input type="hidden" name="_id" value="{{student._id}}">
    <div class="form-group">
        <label>Full Name</label>
        <input type="text" class="form-control" name="fullName" placeholder="Full Name" 
value="{{student.fullName}}">
        <div class="form-group">
            <label>Email</label>
            <input type="text" class="form-control" name="email" placeholder="Email" 
                 value="{{student.email}}">
        <div class="form-row">
                <div class="form-group col-md-6">
            <label>Mobile</label>
            <input type="text" class="form-control" name="mobile" placeholder="Mobile"
                   value="{{student.mobile}}">
                </div>
        <div class="form-group col-md-6">
                <label>City</label>
           <input type="text" class="form-control" name="city" placeholder="City" 
                   value="{{student.city}}">
                </div>
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-info"><i class="fa fa-database"></i> Submit</button>
                <a class="btn btn-secondary" href="/student/list"><i class="fa fa-list-alt"></i> View All</a>
            </div>
</form>

studentController.js: Now we need to also update the following changes in this file also in order to save the updated record.

Important: Replace the previous post request with the following code

router.post('/', (reqres=> {
  if (req.body._id == ''
  insertRecord(reqres);
  else 
  updateRecord(reqres);
});

function updateRecord(reqres) {
  Student.findOneAndUpdate(
    { _id: req.body._id },
    req.body,
    { new: true },
    (errdoc=> {
      if (!err) {
        res.redirect('student/list');
      } else {
        console.log('Error during record update : ' + err);
      }
    }
  );
}

Output 
in Browser- http://localhost:3000/student/list   


Step-21: Now we create a new route in studentController.js for Delete Button to delete the previously stored record.

studentController.js

router.get('/delete/:id', (reqres=> {
  Student.findByIdAndRemove(req.params.id, (errdoc=> {
    if (!err) {
      res.redirect('/student/list');
    } else {
      console.log('Error in student  delete :' + err);
    }
  });
});

list.hbs

<td>
       <a href="/student/{{this._id}}"><i class="fa fa-pencil fa-lg "
          aria-hidden="true"></i></a>&nbsp;&nbsp;&nbsp;
       <a href="/student/delete/{{this._id}}"
   onclick="return confirm('Are you sure to delete this record ?');">
<i class="fa fa-trash fa-lg" aria-hidden="true"></i></a>
</td>