DEV Community

Cover image for Leave Management system Node, MongoDB, Express
Adeeyo Temitayo
Adeeyo Temitayo

Posted on • Edited on

Leave Management system Node, MongoDB, Express

Node.js is eating the world. Many of the largest companies are building more and more of their backend using Node.js, NODEJS IS GROWING WE ARE ALSO GROWING.

This tutorial will take you step-by-step through building a fully functional Leave Management system. Along the way you’ll learn about Express.js, the most popular web framework, user authentication, locking down routes to enforce login restrictions, and performing CRUD operations with a database (creating, reading, updating, and deleting data). This tutorial uses the following technologies but doesn’t require any prior experience:

What we want to do.
We want to build a leave management system where a registered staff of an organization can request leave and the admin(manager or HR) will approve the leave request and an email notification will be sent to the staff.
This article will be broken into Modules.
1:Overview of dependencies, project setup, and server setups.
2:Authentication
3:Authorization
4:Leave request and approval

Main Dependencies
1:Nodejs must be installed
2:MongoDB database
3:Babel
4:sendGrid

Overview of dependencies, project setup, and server setups.
The src folder contains the source file of the project. the purpose of each of the subfolders will be explained subsequently.

Let's get to it.
Create a server.js file in the root folder and copy this into the empty file.

const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
const vm = require("v-response");
const morgan = require('morgan');
const mongoose = require("mongoose")
app.use(express.json());
app.use(morgan('dev'));
const database = 'mongodb://localhost:27017/levemanagementdb';

mongoose.connect((database), {
    useUnifiedTopology: true,
    useFindAndModify: false,
    useNewUrlParser: true,
    useCreateIndex: true,
})
    .then(async () => {
        vm.log("connected to database", database);
    })
    .catch(err => vm.log("error mongodb", err));


app.listen(port, () => vm.log("server running on port:", port));

Enter fullscreen mode Exit fullscreen mode

The server.js file will start up our Nodejs server to run this you can type this to your terminal

node server.js

If all goes well you should get this in your terminal
Alt Text

We need to create an endpoint whereby users can register to achieve that we need to create an authentication folder in our project and create four files inside the folder (auth.model.js,auth.route.js,auth.controller.js, auth.validation.js) in the auth. model file

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


const userSchema = new Schema({
    fullname:{
        type: String

    },
    manager: {
        type: Schema.Types.ObjectId,
        ref: "user"
    },
    email: {
        type: String
    },
    password: {
        type: String
    },
    role: {
        type: String,
        enum: ['manager', 'staff'],
        default: 'staff'
    },
    deleted: {
        type: Boolean,
        default: false
    }
}, {timestamps: true});

const Usermodel = mongoose.model('user', userSchema, 'Users');
module.exports = Usermodel

Enter fullscreen mode Exit fullscreen mode

auth.validation.js:

'use strict';
const Validator = require("validatorjs");

export const UserValidator = {
    /**
     * @param {Object} obj The validation object
     * @return {Object}
     * */
    validateAccount(obj) {
        const rules = {
            email: 'required|string',
            password: 'required|string',
            fullname: 'required|string',
            manager: 'required|string',

        };
        const validator = new Validator(obj, rules);
        return {
            errors: validator.errors.all(),
            passed: validator.passes(),
        }
    },
};
Enter fullscreen mode Exit fullscreen mode

auth.controller.js

const UserValidator = require("./auth.validator");
const _ = require("underscore");

const User = require("./auth.model");
const vm = require("v-response");

/**
 * @controller User authentication controller
 * */
/**
 * @param
 * */

//CREATE ACCOUNT
exports.CreateAccount = async (req, res, next) => {
    try {
        let obj = req.body;
        const validateUserInput = await UserValidator.validateAccount(obj);
        if (!validateUserInput.passed) {
            return res.status(400)
                .json({
                    status: false,
                    code: 400,
                    message: "There's error in your inputs",
                    errors: validateUserInput.errors,
                })
        }
        const checkUserEmail = await User.findOne({email: req.body.email})
        if (checkUserEmail) {
            return res.status(409)
                .json(vm.ApiResponse(false, 409, 'email already exist'))
        } else if (!checkUserEmail) {

            _.extend(obj, {
                password: await vm.hashedPassword(obj.password, 10),
            });
            const account_object = await new User(obj);
            const saveAccount = await account_object.save();
            if (!saveAccount) {
                return res.status(400)
                    .json(vm.ApiResponse(false, 400, "Oops! an error occurr"))
            } else {
                saveAccount.password = null;
                return res.status(201)
                    .json(vm.ApiResponse(true, 200, `account created`, account_object));
            }
        }
    } catch (e) {
        return next(e);
    }

}


//LOGIN
exports.login = async (req, res, next) => {
    try {
        const checkEmail = await User.findOne({email: req.body.email});
        if (!checkEmail) {
            return res.status(400)
                .json(vm.ApiResponse(false, 400, 'email not found'))
        } else {
            const compareEmail = vm.comparepassword(checkEmail.password, req.body.password);
            if (compareEmail) {
                const signtoken = vm.signToken(checkEmail._id, 'yourSecret');
                checkEmail.password = null;
                return res.status(200)
                    .json(vm.ApiResponse(true, 200, "login sucessfull", {user: checkEmail, token: signtoken}))
            }
        }
    } catch (e) {
        return next(e);
    }

};

//list manager so when staff needs to register they can select their managers 
exports.listManagers = async (req, res, next) => {
    try {

        const findMangers = await User.find({role: "manager"})
        if (findMangers) {
            return res.json(findMangers);
        } else if (findMangers.length === 0) {
            return res.json('0 managers')
        } else {
            return res.json("Oops! an error occurr")
        }
    } catch (e) {
        return next(e);
    }
}

Enter fullscreen mode Exit fullscreen mode

auth.route.js

'use strict';
const {Router} = require('express');
const UserController = require("./auth.controller");

const router = Router();
router.post("/create/account", UserController.CreateAccount);
router.post("/account/login", UserController.login);
router.get("/managers/list", UserController.listManagers);

module.exports = router;

Enter fullscreen mode Exit fullscreen mode

So We are done with the first module which enables users to register and login into their account so in the next module we will be working on sending a leave request as a staff and notifying the manager of the leave request via email and also notify the staff when the manager approves or reject leave request for the email notification we will be using SendGrid you can create an account https://signup.sendgrid.com/

Create a Mailer.js file

'use strict';
const Helper = require("sendgrid").mail;
const sg = require('sendgrid')('yourkey');
const vm = require("v-response");
module.exports.sendMail = sendMail;
function sendMail(from, to, subject, content, template) {
    let fromEmail = new Helper.Email(from);
    let toEmail = new Helper.Email(to);
    let emailContent = new Helper.Content("text/html", content);
    let mail = new Helper.Mail(fromEmail, subject, toEmail, emailContent);

    let isEmailSent = false;
    let request = sg.emptyRequest({
        method: 'POST',
        path: '/v3/mail/send',
        body: mail.toJSON()
    });

    sg.API(request, function (err, response) {

        if (err) {
            vm.log("error");
            vm.log("err in sendgrid: ", err);
            isEmailSent = false;
        }
        vm.log("sendgrid body:", response.statusCode);
        isEmailSent = true;
    });
    return isEmailSent;

}

Enter fullscreen mode Exit fullscreen mode

Create a leave folder and create three files(leave.model.js,leave.route.js,leave.controller.js)

Leave.model.js

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

const leaveSchema = new Schema({
    reason: {
        type: String
    },
    staff: {
        type: Schema.Types.ObjectId,
        ref: "user"
    },
    leave_status: {
        type: String,
        enum: ['pending', 'approved', 'rejected'],
        default: "pending"
    }

}, {timestamps: true})


const leaveModel = mongoose.model("leave", leaveSchema, "Leave");
module.exports = leaveModel;

Enter fullscreen mode Exit fullscreen mode

Leave.controller.js

const {sendMail} = require("../util/mailer");

const LeaveModel = require("./leave.model");
const User = require("../authentication/auth.model");
const _ = require("underscore");
const vm = require("v-response");

//request  leave
exports.requestLeave = async (req, res, next) => {
    try {
        const find_user = await User.findById(req.body.staff);
        if (!find_user) {
            return res.status(400)
                .json(vm.ApiResponse(false, 400, 'Invalid user details or unverified account'))
        } else {
            const leaverequestBody = _.extend(req.body, {staff: find_user._id, reason: req.body.reason})
            const createLeaveRequest = await new LeaveModel(leaverequestBody);
            await createLeaveRequest.save();
            const find_manager = await User.findOne({_id: find_user.manager});
            //notify staff manager about leave request
            await sendMail('noreply@leavemanagement', find_manager.email, 'Leave Request', `${find_user.fullname} is requesting for leave`);
            return res.status(200)
                .json(vm.ApiResponse(true, 200, "leave request sent"))
        }
    } catch (e) {
        return next(e);
    }

};

exports.approveLeaveOrRejectLeave = async (req, res, next) => {
    try {
        const findLeave = await LeaveModel.findById(req.query.leave_id);
        const findstaff = await User.findById(findLeave.staff);
        if (!findLeave) {
            return res.status(404)
                .json(vm.ApiResponse(false, 400, 'Leave request not found'))
        } else if (findLeave) {
            if (req.body.approvalstatus === 'approved') {
                await sendMail('noreply@leavemanagement', findstaff.email, 'Leave Approval', `Hello ${findstaff.fullname},your leave request has been approved`);
            } else {
                await sendMail('noreply@leavemanagement', findstaff.email, 'Leave Approval', `Hello ${findstaff.fullname},your leave request has been rejected `);
            }
            findLeave.leave_status = req.body.approvalstatus;
            await findLeave.save();
            return res.status(200)
                .json(vm.ApiResponse(true, 200, "leave request status updated successfully"))
        }
    } catch (e) {
        return next(e);
    }

};
Enter fullscreen mode Exit fullscreen mode

Leave.route.js

'use strict';
const {ManagerChecker} = require("../util/RoleChecker");

const {Router} = require('express');
const LeaveController = require("./leave.controller");

const router = Router();
router.post("/request/leave", LeaveController.requestLeave);
//the ManagerChecker ensures that only a manager can approve or reject a leave request 
router.patch("/update/leave/status", ManagerChecker, LeaveController.approveLeaveOrRejectLeave);

module.exports = router;

Enter fullscreen mode Exit fullscreen mode

Create a MangerCheck.js file

const User = require("../authentication/auth.model");
const jwt = require("jsonwebtoken")

exports.ManagerChecker = async (req, res, next) => {
    let token = req.headers.authorization;
    if (!token) {
        return res.status(400)
            .json("please login to continue")
    }
    if (token !== undefined) {
        let decodeToken = jwt.decode(token);
        let id = decodeToken.id;
        if (id) {
            let user = await User.findById(id);
            if (user && user.role !== 'manager') {
                return res.status(403).json({
                    status: false,
                    code: 403,
                    message: 'You are not authorized to do this action'
                })
            } else {
                return next();
            }
        }
    }
    return next();
}

Enter fullscreen mode Exit fullscreen mode

And lastly, let's seed a manager account so we can test all our hard work if you've made it this far "Ooin you're doing well"

CREATE A seeder.js file

const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
mongoose.Promise = require('bluebird');
const db = 'mongodb://localhost:27017/levemanagementdb';

mongoose.connect(db);
const User = require("../authentication/auth.model");

async function seedUser() {
    const hash = await bcrypt.hash('password123', 10);
    User.create({
        email: "youremail@gmail.com",
        password: hash,
        fullname: "staff manager",
        role: 'manager',
    }).then(user => {
        console.log(`${user} user created`);
    }).catch((err) => {
        console.log(err);
    }).finally(() => {
        mongoose.connection.close();

    })
}
seedUser();

Enter fullscreen mode Exit fullscreen mode

To use the seeder file add this to the script in your package.json file

"seed": "node util/seeder.js"
Enter fullscreen mode Exit fullscreen mode

then run

npm run seed

Enter fullscreen mode Exit fullscreen mode

if all goes well you should see this in your terminal

 user created
Enter fullscreen mode Exit fullscreen mode

then start your Nodejs server

See API Documentation:https://www.getpostman.com/collections/02507f8d63e1342d42f6

See Github Repository:
https://github.com/waletayo/LeaveManagement-System-Node---devto/tree/main

You can also add other features to this simple leave management Restapi or do let me know in the comment section what you look out for next

Thank you Guys!!!

Top comments (1)

Collapse
 
drsimplegraffiti profile image
Abayomi Ogunnusi

Thanks for this...