DEV Community

Cover image for Understanding MVC pattern in Nodejs
Emmanuel Etukudo
Emmanuel Etukudo

Posted on • Updated on

Understanding MVC pattern in Nodejs

This is part two of the Test-driven Development with Nodejs, Express, Mongoose & Jest, in part one, we set up our development environment and ran our first test. In this tutorial, we will focus on building the endpoints using the MVC Architecture.

Definition of terms

MVC - Model View Controler

Model View Controller is a software architectural pattern that involves the separation of the application logic into three interconnected elements the Model, View, and Controller.

Restful API

REST is an acronym for Representation State Transfer, API on the other hand is an acronym for Application Programme Interface. A RESTful API is an architectural style for an application program interface (API) that uses HTTP requests to access and use data.

I surmised you are familiar with the terms involved in this tutorial. Let's get started.

In the previous tutorial, we had a very basic folder structure, with few dependencies. Let's install the required dependencies for our API development.

$ npm i --save-dev body-parser dotenv nodemon
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json file should look like the one below.

{
  "name": "tdd-with-nodejs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "start": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "body-parser": "^1.19.0",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "jest": "^26.6.3",
    "mongoose": "^5.11.9",
    "nodemon": "^2.0.6"
  }
}

Enter fullscreen mode Exit fullscreen mode

You notice we have configured our initialization script to run with nodemon, nodemon will keep track of every change made to our index.js file and refresh our application accordingly. Next, Let's set up a server. create a new file within the root directory of your application named** index.js** and paste the code below.

require('dotenv').config();
const mongoose =  require("mongoose");
//const articles = require("./routes/article.routes");
const bodyParser =  require("body-parser");

const app = exepress();
const port = 8000;

mongoose.connect(process.env.mongoURI, {useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true })
.then(res => console.log(`Connection Succesful ${res}`))
.catch(err => console.log(`Error in DB connection ${err}`));

//body-parser config;
app.use(exepress.json());
app.use(bodyParser.urlencoded({extended: true }));
app.use(bodyParser.json());

app.get("/", (req, res) => {
    res.send(`<h1>Hello!</h1>`)
});

app.listen(port, () => {
    console.log(`Application is listening at port ${port}`);
});

//register the enpoints
//app.use("/api/v1/articles", articles);

Enter fullscreen mode Exit fullscreen mode

Don't forget to create a .env file and add your database URI like so: mongoURI=mongodb+srv://your-db-uri. Next, start the application by typing the command below on your terminal.

$ npm run start
Enter fullscreen mode Exit fullscreen mode

You should get a response on your terminal that reads: Application is listening at port 8000 & Connection Succesful [object Object]. If you open http://localhost:8000 you should get "Hello!" logged to your screen as well.

This tutorial intends to teach you how to properly structure your Nodjs application to fit into the MVC pattern, so, we would be separating our business logic from our controller and routes files. We will learn more about this in the Layered Structure tutorial which is the final tutorial for this series.

Building the Model (Article.js)

Next, let's create our Model. Create a new folder within the root directory of the project, navigate into the folder, and create a new file named Article.js, and copy-paste the code below to create the model for our API.

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


const articleSchema = Schema({

    title:{
        type: String,
        required: true,
    },

    body:{
        type: String,
        required: true,
    },

    article_image: {
        type: String,
        required: false,
    },

    date:{
        type: Date,
        default: Date.now(),
    }

});


module.exports = Article = mongoose.model("Article", articleSchema);
Enter fullscreen mode Exit fullscreen mode

Our model is very basic, it has a title, body, and date object. You can learn more about building MongoDB Schemas using Mongoose by reading the official doc here.

Building the Article Service (ArticleService.js)

To build the ArticleService.js you need to create a folder named services to house our ArticleService.js file. Copy-paste the code below into your ArticleService.js.

const Article = require("../models/Article");

module.exports = class ArticleService{
    static async getAllArticles(){
        try {
            const allArticles = await  Article.find();
            return allArticles;
        } catch (error) {
            console.log(`Could not fetch articles ${error}`)
        }
    }

    static async createArticle(data){
        try {

            const newArticle = {
                title: data.title,
                body: data.body,
                article_image: data.article_image
            }
           const response = await new Article(newArticle).save();
           return response;
        } catch (error) {
            console.log(error);
        } 

    }
    static async getArticlebyId(articleId){
        try {
            const singleArticleResponse =  await Article.findById({_id: articleId});
            return singleArticleResponse;
        } catch (error) {
            console.log(`Article not found. ${error}`)
        }
    }

    static async updateArticle(title, body, articleImage){
            try {
                const updateResponse =  await Article.updateOne(
                    {title, body, articleImage}, 
                    {$set: {date: new Date.now()}});

                    return updateResponse;
            } catch (error) {
                console.log(`Could not update Article ${error}` );

        }
    }

    static async deleteArticle(articleId){
        try {
            const deletedResponse = await Article.findOneAndDelete(articleId);
            return deletedResponse;
        } catch (error) {
            console.log(`Could  ot delete article ${error}`);
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

Building the controller (article.controller.js)

Next, lets' start writing our API endpoints, create a new folder within your root directory named controllers, navigate into the folder, and create a new file named article.controller.js. copy-paste the code below.

const ArticleService = require("../services/ArticleService");

module.exports = class Article{

   static async apiGetAllArticles(req, res, next){
       try {
         const articles = await ArticleService.getAllArticles();
         if(!articles){
            res.status(404).json("There are no article published yet!")
         }
         res.json(articles);
       } catch (error) {
          res.status(500).json({error: error})
       }

   }

   static async apiGetArticleById(req, res, next){
      try {
         let id = req.params.id || {};
         const article = await ArticleService.getArticlebyId(id);
         res.json(article);
      } catch (error) {
         res.status(500).json({error: error})
      }
   }

   static async apiCreateArticle(req, res, next){
      try {
         const createdArticle =  await ArticleService.createArticle(req.body);
         res.json(createdArticle);
      } catch (error) {
         res.status(500).json({error: error});
      }
   }

   static async apiUpdateArticle(req, res, next){
      try {
         const comment = {}
         comment.title        = req.body.title;
         comment.body         = req.body.body;
         comment.articleImage = req.body.article_image

         const updatedArticle = await ArticleService.updateArticle(comment);

         if(updatedArticle.modifiedCount === 0){
            throw new Error("Unable to update article, error occord");
         }

         res.json(updatedArticle);

      } catch (error) {
         res.status(500).json({error: error});
      }
   }

   static async apiDeleteArticle(req, res, next){
         try {
            const articleId = req.params.id;
            const deleteResponse =  await ArticleService.deleteArticle(articleId)
            res.json(deleteResponse);
         } catch (error) {
            res.status(500).json({error: error})
         }
   }

}

Enter fullscreen mode Exit fullscreen mode

Building the routes (article.routes.js)

To communicate with our endpoints we need to set up our routes with corresponding requests. Create a new folder named routes, inside the folder, create a new file named article.routes.js. You can choose to name the folder whatever you like but it is always nice to maintain meaningful directory and file names.

const  express =  require("express");
const router = express.Router();
const ArticleCtrl = require("../controllers/article.controller");


router.get("/", ArticleCtrl.apiGetAllArticles);
router.post("/", ArticleCtrl.apiCreateArticle);
router.get("/article/:id", ArticleCtrl.apiGetArticleById);
router.put("/article/:id", ArticleCtrl.apiUpdateArticle);
router.delete("/article/:id", ArticleCtrl.apiDeleteArticle);

module.exports =  router;
Enter fullscreen mode Exit fullscreen mode

If you've followed the tutorial up to this point, your folder structure should actually look like the snippet below.

├── tdd-with-nodejs
 ├── controllers
     ├── article.controller.js
 ├── models
     ├── Article.js
 ├── routes
     ├── article.routes.js
 ├── services
     ├── articleService.js
├── test
Enter fullscreen mode Exit fullscreen mode

Putting it all together

When working with the MVC pattern always ensure you maintain the Separation of Concern (SoC) technique. Separation of concerns (SoC) is a design principle for separating software applications into distinct sections such that each section addresses a separate concern. A concern is a set of information that affects the code of a Software Application. We will dive deeper into this topic in the next tutorial which is the last for this series.

Before we conclude let's test one of the endpoints. Send a POST request to the /api/v1/articles endpoint using an API testing tool of your choice. In my case, Postman You should get the response object as a response for your newly created article similar to the snippet below.

Screenshot 2021-01-06 at 18.25.03.png

That's all for this tutorial. congratulations on making it thus far in this series. See you in the next tutorial.

Top comments (30)

Collapse
 
betula profile image
Slava Birch • Edited

Great article!
I will allow myself to make little one suggestion to your article for will make it a diamond.
To enable syntax highlighting in a code section, you needs to open your code block as

```javascript
code
```
Its will look likes

const app = exepress();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
algodame profile image
Chiamaka Ojiyi

Very helpful. Will try it out

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

Thank you so much for this input, I will update the article shortly.

Collapse
 
pemo11 profile image
Peter

Great article and a good example with a modern approach. I learned a lot. So thanks for putting all this together.

Few questions though:

Does the updateArticle API really works? I think there is a parameter mismatch between updateArticle() in ArticleService.js and apiUpdateArticle in article.controller.js
new Date() without now() in ArticleService.js?
how is updating an article possible without the _id?`

and what about the views-part?

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

Good news for tech devs!

I built and deployed a CLI to help you architect a Nodejs application using the MVC design pattern & the Layered Architecture. Contributions are welcomed #cli #npm #npmpackage
install using: npm i -g @eetukudo/node-mvc
npmjs.com/package/@eetukudo/node-mvc

Collapse
 
digitalprimeint profile image
Marco Castillo

Emmanuel this is a great post!

I would like to get your honest feedback of my MVC framework. I have several years (6) applying the MVC concept on top of the express module. This is the latest version I made with ES6 best practices.

Dev.to Article
dev.to/digitalprimeint/mvc-framewo...

Documentation:
npmjs.com/package/plugdo-mvc

It could be 10 min of your time!

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

I will install the package and get back.

Collapse
 
727021 profile image
Andrew Schimelpfening

This is a great example of MVC with Node.
What’s the reasoning behind exporting a class with all static methods instead of just exporting the methods themselves?

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

It simply developer preference, The first thing that came to mind was the "Don't Repeat Yourself (DRY)" approach. Exporting each method can as well become verbose. Let me know if you can improve the code. Thanks.

Collapse
 
osairisali profile image
AAH

I think exporting a class with all its methods is quite bulky if we only need one or two of them. Security concern might also exposed. Anyway, it's a good article 👍 Thank you

Collapse
 
tonyscruze profile image
TonySCruze

Very nice article. I will restructure my current project to follow this "layered" structure. I'm trying to become more aware of separating concerns. Hopefully, I'll reap the benefits early on. Thank for the article 🙂

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

Thank you so much @tonyscruze . The first benefit is that you can write testable code on the fly.

Collapse
 
benrochlin profile image
ben-rochlin

This is by far the best overview of MVC architecture in express that I have come across. Seriously, like a year of trying to get my head around it and this overview and actually really concise and clever syntax (like use of static class methods) has really helped me figure this out! thank you so much 🤟🤟💕

Collapse
 
kiraniyerdev profile image
Kiran Iyer

I've just started exploring the MVC architecture, it seems a little overwhelming linking the different folder/file structures to each other. This is a great article to adding a bit more clarity to my confusion but again I've come across a whole new "services" folder and I've not used it in my CRUD todo application.

What I'm uncertain of is, where to begin when creating an app using the MVC concept.
I tend to get confused about which file is to be linked/imported where. Perhaps if you can give me a few pointers that'd be great or maybe point me to another article or perhaps a resource. Thanks

Collapse
 
quyle92 profile image
quyle92 • Edited

Emmanuel this is a great post!
But I would like to ask a question.
If we already had try catch block at Controller, then do we need to implement them again in Service layer? There is a similar case study on a youtube channel name "Laravel Daily" (link https://www.youtube.com/watch?v=0hyRw7zeVcs&t=182s) and at 4:07 the video author (Povilas) said "A Service should just throw an exception". So is it not a good practice to catch exception anywhere else beside Controller?
What do you think about it?
Thank you,
Quy

Collapse
 
thxv3n0lvl profile image
thxv3n0lvl

A reference on how to configure Mongo locally or on cloud will be great, this made me stop reading it as tutorial and switch to a "he's just explaining a pattern" mindset. Good enough though, thanks!

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

Thank you @thxv3n0lvl for the feedback, I will write an article on how to create a MongoDB database on MongoDB-Atlas and managing MongoDB databases with MongoDB compass.

Collapse
 
phongduong profile image
Phong Duong

Great tutorial. Thank you for sharing

Collapse
 
xementor profile image
xementor

It it good writting,
when i am reading i thinked ArticleService.js file is the Controler part of MVC.
but it was not. then ArticaleSErvice.js is in which part of MVC?

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

No, The ArticleService.js is in a separate folder name services, it should be within the root directory of the project. This is because we are using Layered Architecture, there is a need to separate the business logic from the controller.

Collapse
 
esilyzhang profile image
esily

Thank you for sharing, it is very helpful for me to learn MVC.
Can I translate your article into Chinese and allow it to be posted on the HelloGithub(hellogithub.com/) community platform?

Collapse
 
eetukudo_ profile image
Emmanuel Etukudo

Yes, you can. Let me know how it comes out. Best.

Collapse
 
raphydev profile image
Raphael Blehoue

it good writting

Collapse
 
lbian_prasoon profile image
Prasoon Shukla

Where to place middlewares in this project structure?

Collapse
 
ajyku profile image
AjayK • Edited

article.routes.js should be the place for the middleware like

var auth = require('../middleware/auth');
router.put("/article/:id", auth.isAuthenticated(), ArticleCtrl.apiUpdateArticle);
Enter fullscreen mode Exit fullscreen mode