Day-3 of learning Express JS

Today I've learnt about the MVC architecture in Express JS. Let me briefly describe about my learning.

What is MVC architecture?

MVC stands for Model, View, Controller and in this architecture we build the application as follows:-

  • Model: it defines the schema of a database and defines its constraints. It is a dynamic datastructure that holds the schema objects in place for the database.

  • View: it is used to render (like: .ejs files, .hbs files, .pug files, etc.) to the client-side i.e. web pages which are presented to the user. Thus, handling presentation logic of the application.

  • Controller: it Handles Application logic of the application model, accepts and validates HTTP requests and also converts the requests into commands for the model and views.

Creating folder structure for an illustration

The following tree or folder structure illustrates an example of how an MVC looks for an express application:-

project-root/
│
├── controllers/
│   ├── userController.js       // Controller for user-related logic
│   └── ...
│
├── models/
│   ├── User.js                 // Model for user data
│   └── ...
│
├── routes/
│   ├── index.js                // Main router file
│   └── userRoutes.js           // Router file for user-related routes
│   └── ...
│
├── views/
│   ├── layouts/
│   │   └── main.hbs            // Main layout template
│   ├── user/
│   │   ├── index.hbs           // View for user dashboard
│   │   ├── login.hbs           // View for user login form
│   │   ├── register.hbs        // View for user registration form
│   │   └── ...
│   └── ...
│
├── public/
│   ├── css/                    // CSS files
│   ├── js/                     // JavaScript files
│   └── img/                    // Image files
│
├── app.js                      // Entry point of the application
├── settings.js                 // Configuration file
└── package.json                // Project metadata and dependencies

This will segregate our code and increases the readability of the application logic.

Models

The models contain files defining the structure (schema) and behavior of data to be posted onto a database. They are responsible for managing the application's data, including its validation, manipulation (CRUD operations), and interaction (like querying data) with the database.

const mongoose=require('mongoose')

//Schema for user collection in mongoDB
const userScehma=new mongoose.Schema({
    first_name: {
        type: String,
        required:true,
    },
    last_name: {
        type:String
    },
    email: {
        type:String,
        required:true,
        unique: true
    },
    gender: {
        type:String,

    },
    job_title:{
        type:String
    } 
})

const User=mongoose.model('User',userScehma);
module.exports= User;

Controllers

The controller will contain the code which we are going to send to the user. Whenever a specific route if hit by the user, the corresponding controller function linked with that route in the routes folder will get fired. Controllers basically demonstrate CRUD operations.

const User=require('../models/user')

//it fetches or reads all the users from DB
async function getAllUsers(req,res){
    const allUsers= await User.find({});
    return res.status(200).json(allUsers)
}

//it fetches or reads only one user with the specified ID from DB
async function getUserById(req,res){
    const user= await User.findById(req.params.id) 
    console.log(`user: ${user.first_name} ${user.last_name} is requested`)
    return res.json(user)
}

//it creates a new user to the DB
async function postUser(req,res){
    const body=req.body;
    if(
        !body.first_name ||
        !body.last_name ||
        !body.email ||
        !body.gender ||
        !body.job_title 
    ){
        return res.status(400).json({msg: "All the fields are required..."})
    }
    const  result = await User.create({
        first_name: body.first_name,
        last_name: body.last_name,
        email: body.email, 
        gender: body.gender,
        job_title:  body.job_title
    })
    console.log(result)
    return res.status(201).json({msg:"success"})
}

//updates user by ID specified
async function patchUserById(req,res){
    await User.findByIdAndUpdate(req.params.id, { last_name: "Changed" });
    return res.json({status:"success"})
}

//deletes user by ID specified
async function deleteUserById(req,res){
    await User.findByIdAndDelete(req.params.id);
    return res.json({status:'success'})
}

//exporting these modules to be imported in routes
module.exports={
    getAllUsers,
    getUserById,
    postUser,
    patchUserById,
    deleteUserById
}

Routes

The router is going to be the bridge between app.js and the controllers. It just basically tells, the get request to execute this method that is inside the controller and for the post request to execute that method that is inside the controller.

const express=require('express')
const router=express.Router();

router.route('/')
.get(async (req,res)=>{
    const allUsers= await User.find({});
    return res.status(200).json(allUsers)
})
.post(async (req,res)=>{
    const body=req.body;

    if(
        !body.first_name ||
        !body.last_name ||
        !body.email ||
        !body.gender ||
        !body.job_title 
    ){
        return res.status(400).json({msg: "All the fields are required..."})
    }

    const  result = await User.create({
        first_name: body.first_name,
        last_name: body.last_name,
        email: body.email, 
        gender: body.gender,
        job_title:  body.job_title
    })

    console.log(result)

    return res.status(201).json({msg:"success"})

})


router.route('/:id')
.get(async (req,res)=>{
    const user= await User.findById(req.params.id) 
    console.log(`user: ${user.first_name} ${user.last_name} is requested`)
    return res.json(user)
})
.patch( async (req,res)=>{
    await User.findByIdAndUpdate(req.params.id, { last_name: "Changed" });
    return res.json({status:"success"})
})
.delete(async (req,res)=>{
    await User.findByIdAndDelete(req.params.id);
    return res.json({status:'success'})
})

module.exports=router;

Server or app.js code:

const express =require('express')
const userRoutes=require('./routes/user_routes')
const connectDB = require('./configs/connection')
const database = require('./configs/settings')
const {logMiddleware}=require('./middlewares')  //here no need to mention index.js since react knows by default

const app=express()
const PORT=process.env.PORT || 5000

//Connect with DB
connectDB(database._url)
    .then(()=>console.log("connected to Mongodb"))
    .catch((err)=>console.log(err))

//Middlewares- plugin
app.use(express.urlencoded({extended: false}))
app.use(logMiddleware("log.txt"))   //Fires a log to the console when any kind of req is received


//Routes
app.use('/api/users',userRoutes); //this basically means that if a request comes on '/user' then append the routes from userRoutes

app.listen(PORT,()=>{
    console.log(`server is live at http://localhost:${PORT}`)
})

Conclusion

Why are we using this segregated coding approach with MVC, we could have just kept everything in the server code file (server.js or app.js whatever you prefer to say). The reasons are:

  1. Modularity: By organizing code into separate folders for models, views, and controllers, each component of the application becomes isolated and modular. This modular structure makes it easier to understand, maintain, and update the codebase, as changes to one component are less likely to affect others.

  2. Scalability: Segregating code into separate folders facilitates scalability by allowing developers to add new features or modify existing ones without impacting the entire codebase. Each component can be expanded or modified independently, making it easier to scale the application as requirements change or grow.

  3. Code Reusability: MVC promotes code reusability by encapsulating common functionality within models, views, and controllers. Components such as models, which handle data access and manipulation, can be reused across different parts of the application, reducing redundant code and development effort.

  4. Improved Collaboration: MVC's folder structure provides a clear and standardized organization of code, making it easier for developers to collaborate on projects. Team members can quickly locate and understand the purpose of different files and folders, leading to improved productivity and collaboration.

  5. Testing: Separating code into different folders facilitates unit testing and other forms of testing by allowing developers to isolate and test individual components independently for any bugs which can be easily hunted and fixed.

Social handles:
LinkedIn
Githu
b
Twit
ter