MVC or Model-View-Controller is a software design pattern to implement web applications. In this post, we will learn how to create a NodeJS Express MVC application from scratch.
If you are new to the NodeJS and Express ecosystem, you can first check out this post on getting started with ExpressJS.
1 – What is the MVC Architecture?
The MVC or Model-View-Controller architectural pattern advocates a separation of concern between various application components. This separation of concern results in better division of labour. It also leads to easier maintenance.
There are three parts of the MVC design pattern.
- The Model – It defines the data part of the application. Anything to do with the representation of the data or saving/fetching data is the concern of the Model. In a typical e-commerce application, you can think of the Model being the product data.
- The View – It defines how the app’s data should be displayed to the user. The view part of MVC concerns itself with the presentation of the data to the user. In any web application, view is the interface shown to the user. It is good practice to decouple the view from the rest of the application code.
- The Controller – This is the glue between model and view. Basically, the controller contains the logic to update the view or the data depending on user changes. In the scheme of things, the controllers acts as a sort of middleman.
From an application context, controllers also have routers sitting on top of them. Basically, routers are like handlers for incoming requests. In other words, a router determines which controller should handle a given request.
Now that we understand the basics of MVC architectural pattern, let us work on building a demo application for the same.
2 – NodeJS Express MVC Example
For our demo purpose, we will create a shop application. The purpose of this application is to display a list of products to its customers. However, we will utilize the MVC principles to build our application.
- For the Model part, we will create a product model.
- For the View, we will be using the EJS templating engine.
- For the Controller part, we will create one controller for managing products and another for displaying the 404 page.
To get started with the project, we will install a few necessary packages.
$ npm install --save express ejs body-parser
Below is the package.json
file for the project.
{
"name": "nodejs-express-mvc-demo",
"version": "1.0.0",
"description": "NodeJS Express MVC Sample",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js",
"start-server": "node app.js"
},
"author": "Saurabh Dashora",
"license": "ISC",
"devDependencies": {
"nodemon": "^1.18.3"
},
"dependencies": {
"body-parser": "^1.18.3",
"ejs": "^3.1.8",
"express": "^4.16.3"
}
}
Apart from the dependencies, we have a few basic scripts for starting the application server.
3 – NodeJS Express MVC – Model Creation
Now that our project is ready, let us first create the Model for our application. Since we are building an product website, we will create a class to store product information.
To do so, we will create a special folder models
within the root directory of our project. Inside that folder, we create a file product.js
.
const fs = require('fs');
const path = require('path');
const p = path.join(
path.dirname(process.mainModule.filename),
'data',
'products.json');
const getProductsFromFile = cb => {
fs.readFile(p, (err, fileContent) => {
if (err) {
return cb([])
} else {
cb(JSON.parse(fileContent))
}
})
}
module.exports = class Product {
constructor(t) {
this.title = t
}
save() {
getProductsFromFile(products => {
products.push(this);
fs.writeFile(p, JSON.stringify(products), err => {
console.log(err)
})
});
}
static fetchAll(cb) {
getProductsFromFile(cb)
}
}
We export a class Product from this particular file. Apart from the constructor, we have special methods to save a new product and fetch all products from the file.
As you can notice, we are storing the product data in a file within our project. In a real world application, this can be a database. However, the concept remain the same.
4 – NodeJS Express MVC – View Creation
After the model, we can now create the view part of our application. For views, we are using the templating engine EJS.
To keep the view files, we create another folder views within the root of our project. Inside the folder, we create various template files for adding a product view, product list view and the 404 page.
See below the add-product.ejs
file.
<%- include('includes/head.ejs') %>
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
<%- include('includes/navigation.ejs') %>
<main>
<form class="product-form" action="/admin/add-product" method="POST">
<div class="form-control">
<label for="title">Title</label>
<input type="text" name="title" id="title">
</div>
<button class="btn" type="submit">Add Product</button>
</form>
</main>
<%- include('includes/end.ejs') %>
Next, we have the shop.ejs
for displaying the product list.
<%- include('includes/head.ejs') %>
<link rel="stylesheet" href="/css/product.css">
</head>
<body>
<%- include('includes/navigation.ejs') %>
<main>
<h1>My Products</h1>
<p>List of all the products...</p>
<% if (prods.length > 0) { %>
<div class="grid">
<% for (let product of prods) { %>
<article class="card product-item">
<header class="card__header">
<h1 class="product__title"><%=product.title %></h1>
</header>
<div class="card__image">
<img src="https://cdn.pixabay.com/photo/2016/03/31/20/51/book-1296045_960_720.png" alt="A Book">
</div>
<div class="card__content">
<h2 class="product__price">$19.99</h2>
<p class="product__description">A very interesting book about so many even more interesting things!
</p>
</div>
<div class="card__actions">
<button class="btn">Add to Cart</button>
</div>
</article>
<% } %>
</div>
<% } else { %>
<h1>No Products Found!</h1>
<% } %>
</main>
<%- include('includes/end.ejs') %>
Lastly, we have the 404.ejs
file.
<%- include('includes/head.ejs') %>
</head>
<body>
<%- include('includes/navigation.ejs') %>
<h1>Page Not Found!</h1>
<%- include('includes/end.ejs') %>
Within these view templates, we are also using the concept of EJS partials. The partial files are meant to increase reusability of code and remove duplication. We place the partial files within the includes
folder inside the views
directory.
First, we have the partial for the HTML head. We name it head.ejs.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= pageTitle %></title>
<link rel="stylesheet" href="/css/main.css">
Next, we have the EJS file for the navigation bar. We name it navigation.ejs
.
<header class="main-header">
<nav class="main-header__nav">
<ul class="main-header__item-list">
<li class="main-header__item"><a class="<%= path==='/' ? 'active': '' %>" href="/">Shop</a></li>
<li class="main-header__item"><a class="<%= path==='/admin/add-product' ? 'active': '' %>" href="/admin/add-product">Add Product</a></li>
</ul>
</nav>
</header>
Lastly, we have the partial file for the HTML ending. We name it end.ejs
.
</body>
</html>
With this, the view part of our application is done.
5 – NodeJS Express MVC – Controller Creation
Now is the time to create the controllers for our NodeJS Express MVC demo application. To organize our code properly, we will keep the controllers inside the controllers
folder within the root of our project.
Below is the controller for managing the product routes.
const Product = require('../models/product')
exports.getAddProduct = (req, res, next) => {
res.render('add-product', {
pageTitle: 'Add Product',
path: '/admin/add-product'
});
};
exports.postAddProduct = (req, res, next) => {
const product = new Product(req.body.title);
product.save();
res.redirect('/');
}
exports.getProducts = (req, res, next) => {
const products = Product.fetchAll((products) => {
res.render('shop', {
prods: products,
pageTitle: 'Shop',
path: '/'
});
});
}
Basically, here we have the necessary logic to render the add product view and the product list view. Here, we are using the EJS template names while calling the res.render()
function.
Next, we have another controller for the 404 page. See below:
exports.get404Page = (req, res, next) => {
res.status(404).render('404', {pageTitle: "Page Not Found", path: ""})
}
Here, we simply render the 404.ejs
template.
As we mentioned earlier, the controllers are dependent on the routers. Therefore, we can now create routers for our application using Express router. We keep the router files in the routes
directory.
First, we have the routes for adding product.
const express = require('express');
const productsController = require('../controllers/products');
const router = express.Router();
// /admin/add-product => GET
router.get('/add-product', productsController.getAddProduct);
// /admin/add-product => POST
router.post('/add-product', productsController.postAddProduct);
module.exports = router;
Basically, here we are using the productController
we just created.
Next, we have a separate router for the list products functionality.
const path = require('path');
const express = require('express');
const productsController = require('../controllers/products')
const router = express.Router();
router.get('/', productsController.getProducts);
module.exports = router;
6 – NodeJS Express MVC – The Main File
Lastly, we can bring everything together by creating the main file of our application. We name this file app.js
.
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const errorController = require('./controllers/error.js');
const app = express();
app.set('view engine', 'ejs');
app.set('views', 'views');
const adminRoutes = require('./routes/admin');
const shopRoutes = require('./routes/shop');
app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/admin', adminRoutes);
app.use(shopRoutes);
app.use(errorController.get404Page);
app.listen(3000);
Basically, in this file, we configure the EJS view engine. Also, we import the necessary controllers and routers and include them into the Express application context.
Finally, we configure the application to run on port 3000.
With this, we can now test our application using npm run start
command. The application will be available on http://localhost:3000
. However, don’t forget to create a folder data
for keeping the product file products.json
.
Conclusion
With this, we have successfully completed our NodeJS Express MVC demo application. We implemented Model, View and Controller to demonstrate the effectiveness of the MVC pattern. Also, we created routers to call the appropriate controllers.
The code for this application is available on Github along with the CSS classes we have used.
Want to learn more NodeJS? Check out this post on how to implement NodeJS Express Login Authentication.
If you have any comments or queries about this post, please feel free to mention them in the comments section below.
0 Comments