DEV Community

loading...
Cover image for Understanding MVC pattern in Nodejs

Understanding MVC pattern in Nodejs

eaetukudo profile image Emmanuel Etukudo Updated on ・5 min read

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.

Discussion (20)

pic
Editor guide
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
eaetukudo profile image
Emmanuel Etukudo Author

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

Collapse
eaetukudo profile image
Emmanuel Etukudo Author

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
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
eaetukudo profile image
Emmanuel Etukudo Author

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

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
eaetukudo profile image
Emmanuel Etukudo Author

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
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
eaetukudo profile image
Emmanuel Etukudo Author

I will install the package and get back.

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
memphis1983 profile image
Kieran

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
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
eaetukudo profile image
Emmanuel Etukudo Author

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
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
eaetukudo profile image
Emmanuel Etukudo Author

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
eaetukudo profile image
Emmanuel Etukudo Author

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

Collapse
phongduong profile image
Phong Duong

Great tutorial. Thank you for sharing

Collapse
raphydev profile image
Raphael Blehoue

it good writting