Unveiling The MVC Structure

Introduction

Websites evolved in a very amazing fashion to be both stunning and cutting edge as web apps today, not just due to tools and technologies, but also due to organization. The way the code sites were arranged in the last twenty years have also evolved to more robust file organization patterns. One of such is the file patterns is termed Model View Controller (MVC), and after this article, you won't need another article to start organizing your dream projects properly

Pre-requisite

  • HTML

  • Javascript

  • NodeJS and ExpressJS

Model View Controller (MVC) -An Overview

MVC is just a way to organize our code bases to be more efficient and robust. It is a pattern to allow more feature implementation and iteration by many software developers while still maintaining structure and organization in our codebase. MVC entails splitting a large application into specific sections that serve different purposes under Model, View and Controller paradigms.

Model is the section of the application that handles everything about databases, data modelling and schema. Controller is the section that receives requests from the user, interfaces with the database code of the model section, receives the data from model, and interfaces with View, the part that renders the output based on the data generated by the received from the controller.

The basics of MVC are:

  1. CONTROLLER receives request from user.

  2. CONTROLLER sends request to MODEL section for data.

  3. MODEL sends data back to the CONTROLLER.

  4. CONTROLLER sends data from MODEL to VIEW.

  5. VIEW dynamically renders an output or page based on the data it receives from CONTROLLER.

  6. VIEW sends back output to CONTROLLER.

  7. CONTROLLER sends response back to user.

The functions of different sections are: Controller:

  • Handles request flow : receives a request and sends a request to fetch data.

  • Never handles data logic

  • Sends back presentation and data to the user i.e browser

Model:

  • Handles data logic

  • Interacts with database

View:

  • Handles data presentation

  • A template file that dynamically renders the data sent by the controller

  • Sends its presentation to the controller

The key thing to note is that the model and view never directly interface with each other, but through the controller.

Demo

The true test of learning is the ability to implement. In a real world example, if a user's browser wanted to access a login page, the controller can direct the view section to render any file based on conditions met, so it does not only serve as a data retriever from the model section, but a kind of traffic warden. For example, if a user does not have a facebook account, but tries to log in, Facebook's controller will of course ask the model to transverse its database for that user, and if the user's details do not exist, it can also tell the view page to print out an error page based on the condition that the user details does not exist.

Let's write some code for a mockup API using real world technologies. Before we move on, you should know that there is not one standard way of organizing MVC, you can do it with as many folders as you want as long as you separate, the backend function code, the user interface code, and database logic from each other.

Without MVC

Normally in a project without MVC, a routing file will hold all the routes and the logic for controlling them.

import {express} from "express";
const router = express.Router();

router.get("user/all", (req,res)=>{
    const userList = User.findAll().exec();
    res.json(userList)
});

router.get("user/byId/:id", (req,res)=>{
    const user = User.findById(req.params.id).exec();
    res.json(user);
})

router.post("user/create",(req,res)=>{
    const User = req.body;
    User.save((err,user)=>{
        if(err){
            res.send(err);
        } else{
            res.json(user);
        }
    });
    res.json(req.body)
})
  • You don't have to understand the code. It is just a snippet from a larger Node JS code.

  • What is you should be concerned about is how the code is structured, the router file receives requests from the user and still has all the functionality contained within the express requests.

  • The MVC principle urges for the router file to only receive the routing requests and call a back-end function specified in our controller section.

With MVC

Step One: First of all, we have to specify our folders, to maintain clarity, I will just use the code snippet from the preceding section. We will use four folders, controller,model,view and routes

/controller
  userController.js
/model
  userBase.js
/view
  userPage.html
/routes
  userRoute.js
  • For our file structure, we added the routes folder to have a list of all possible routes that might correspond to the different endpoints of our API. In the /routes, there is a UserRoute file for a dummy user end point.

  • The view folder will contain an output file of either dynamically generated HTML, or front-end dynamic pages like ejs or jsx. Something that can update itself to the user after receiving direction from the controller.

  • In the routes and controller sections, the userRoute.js and userController.js will work hand in hand to handle all functions for the dummy user route.

Step two: Change the code in the routes/userRoute.js to only receive requests and instead call a function from the UserController file in the controller section.

import {express} from "express";
const router = express.Router();

import UserController from "../controller/UserController"

router.get("user/all",UserController.GetAllUSers)
router.get("user/byId/:id",UserController.GetUSer)
router.post("user/create",UserController.CreateUserProfile)
  • The takeaway here is that we are no longer defining our functions in routes/userRoute.js, but only receiving the requests and calling the functions on the controller/userController.js.

Step three: Create the functions being called in the userRoute.js file.

// controllers/userController.js

import User from "../models/user.js";

const userController = {
 getAllUsers: async (req, res) => {
   try {
     const userList = await User.find().exec();
     res.json(userList); // Take note of this res.json() method
   } catch (err) {
     res.status(500).json({ error: "Internal Server Error" });
   }
 },

 getUserById: async (req, res) => {
   try {
     const user = await User.findById(req.params.id).exec();
     res.json(user);
   } catch (err) {
     res.status(500).json({ error: "Internal Server Error" });
   }
 },

 createUser: async (req, res) => {
   try {
     const newUser = new User(req.body);
     const user = await newUser.save();
     res.json(user);
   } catch (err) {
     res.status(500).json({ error: "Internal Server Error" });
   }
 },
};

export default userController;
  • First of all, notice that we are importing a User model from with this line import User from "../models/userBase.js";

  • This User model contains all the data operations and logic for this specific feature, that can be used in the controller section.

  • If we have a feature that warrants us to save some data, we use the User model, like on this line const userList = await User.find().exec();

  • Then the most important thing is that all the backend functions,getAllUsers,getUserById, and createUser required for the user route is being called in the controller section, and interacting with the model section and exported to the routes section.

  • The res.json() method is used to store the response gotten from the model section and send it to the view section. (More on JSON here).

  • By now you should be noticing a trend, that the controller section is the only section interacting with the route and model sections, and will do the same for the view section. So the MVC pattern only warrants that no other section should interface with each other, without passing through the controller as the middleman.

Step four: To keep things simple, let's say you want to render a new webpage in HTML due to data sent from the model section to the controller section, you should be faced with two problems, the design of the webpage, which is the hard part, and how to send a response to the view section from the controller which is the easy part. Let's do the easy part below:

<!-- views/userPage.html -->

<!DOCTYPE html>
<html>
<head>
  <title>User List</title>
</head>
<body>
  <h1>User List</h1>
  <ul id="user-list"></ul>

  <script>
    // Fetch user data from the server
    fetch("/users/all")
      .then(response => response.json())
      .then(users => {
        const userListElement = document.getElementById("user-list");
        users.forEach(user => {
          const li = document.createElement("li");
          li.innerText = `Name: ${user.name}, Age: ${user.age}, Email: ${user.email}`;
          userListElement.appendChild(li);
        });
      })
      .catch(error => console.error(error));
  </script>
</body>
</html>
  • In the <script></script> tag, you call a fetch method that sends a fetch request for data from the controller function called for the users/all route. Remember the controller file used JSON to store and send the request to the view section, this is the view section file now receiving that data with a fetch request.

  • After receiving the data from the controller, it can render data and User Interface Components to the user.

Quick Tips

  • If you are wondering how the controller back-end function is able to send data it received from model to the view section, know that express works in a way where any function called on a particular route will only store data and send data of that particular route to used in whatever way, in this case, the view section that receives the data via the fetch request.

  • In MVC, it is advisable to have as many folders, and files that aid the either the Model, View or Controller functions. You can have as many as you want, but it is most important to have a file in the model, view, controller and route folder for every specific function. For example, we had a user route, and corresponding userController.js, UserRoute.js, userBase.js files for it, so if we want to add an admin route, it is design friendly to have corresponding admin controller, routes, model and view files.

Conclusion

The Model View Controller (MVC) pattern provides a clear, organized approach to structuring your code, making it easier to maintain, extend, and collaborate on web applications. By separating the concerns of data handling (Model), business logic and control flow (Controller), and presentation (View), MVC allows you to keep your code modular and scalable. Whether you’re building a simple web app or a complex application with multiple contributors, adopting MVC practices ensures that your project remains manageable as it grows. Now that you understand the fundamentals and have seen how to implement MVC in a Node.js application, you can confidently structure your projects to be both efficient and maintainable. Happy coding!