Create Forgot Password Functionality With Mail In Nodejs

In this post i will show you how you can create an nodejs api with mysql database for password reset or forgot password functionality

I hope you already have nodejs project setup with all route , controller structure.

Inside route add one route for reset-password like below.

router.post('/reset-password', loginController.forgotPasswordHandler)

and login for the forgotPasswordHandler will be like below

const { ForgotPassword } = require("../services/users.services");

const forgotPasswordHandler = async (req, res) => {
    const email = req.body.email;
    if (email == "") {
        res.status(400).json({ "err": "Email is required" })
        return
    }
    try {
        let user = await ForgotPassword(email)       
        res.status(200).json("Password reset mail sent, please check your mail.")

    } catch (error) {
        console.log(error.message)
        res.status(500).json(error.message)
    }
}

As you can see i am checking for email to be present if not then simply reurning statucode 400 which is bad request.

I have also created a separate folder for storing service like ForgotPassword in separate js file

You can ask me why in separate file i could have put that login in same controller function. Then my answer will be for unit testing.

If you will detach your logics in separate folder then you can easily unit test that function.

service/users.service.js

const bcrypt = require('bcrypt');

const { getUserByEmail } = require('../repository/users.repository');
const { sendMail } = require('./mail.service');
const fs = require("fs")
const path = require("path")
const Handlebars = require("handlebars");
const ResetPassword = require('../models/mysqldb/reset-password.models');

async function ForgotPassword(email) {
    let userExist = await getUserByEmail(email)
    if (userExist.length > 0) {
        let user = userExist[0];
        const userEmail = user.email;
        console.log(user)
        try {

            // creating data for sending mail
            const data = {
                "fromEmail": process.env.APP_FROM_EMAIL,
                "fromName": process.env.APP_FROM_NAME,
                "subject": "Reset Password",
                "body": ``,
                "toEmail": [user.email]
            }
            console.log(data)

            //initializing ResetPassword() model for storing user_id and token in the database.
            let resetPassword = new ResetPassword();

            // creating token 
            let token = await bcrypt.hash(user.email, 1);

            // creating base64 encoded token because hash can contain "/" symbol which will give problem if passed as url param.
            token = Buffer.from(token).toString('base64')

            // storing user_id and token in `reset_password` table
            let result = resetPassword.create([{
                "user_id": user.id,
                "token": token
            }])

            // creating redirect url for navigating user to fill new password
            let actionUrl = `http://${process.env.FRONTEND_URL}/reset/${token}`;
            console.log(actionUrl)

            // reading template file for sending in mail
            const templateStr = fs.readFileSync(path.resolve(__dirname, '../views/mails/resetPassword.hbs')).toString('utf8')
            const template = Handlebars.compile(templateStr, { noEscape: true })

            // parsing template file for changing variables with values

            const html = template(
                {
                    "name": `${user.firstname} ${user.lastname}`,
                    "action_url": actionUrl

                }
            )
            data.body = html;

            // sending mail using nodemailer library
            let resultMail = await sendMail(data);
            console.log(resultMail)

        } catch (error) {
            console.log(error);
            throw new Error("Something wrong in sending mail");
        }

    } else {
        throw new Error("User not found with that email")
    }

}

It is quite lengthy as you can see but everything is commented with proper description.

what i am doing is parsing handlebar template file which have the reset password mail template and using nodemailer library just sending the mails.

for template i have used postmarkapp.com below is the link for template file

Template file will look like below reset password mail template

you should put that html file in views/mails/resetPassword.hbs

Dont forgot to change the variable names compatible with handlebars like {{name}} should be replaced by {{{name}}} in template.

otherwise parsing wont work.

Now i am giving you the code for sending sendMail() function.

services/mail.service.js


const { createTransport } = require("nodemailer")

async function sendMail(data) {

    let transporter = createTransport({
        host: process.env.MAIL_HOST,
        port: process.env.MAIL_PORT,
        secure: false, // true for 465, false for other ports
        auth: {
            user: process.env.MAIL_USERNAME, // generated ethereal user
            pass: process.env.MAIL_PASSWORD, // generated ethereal password
        },
    });

    // send mail with defined transport object
    let info = await transporter.sendMail({
        from: `"${data.fromName}" <${data.fromEmail}>`, // sender address
        to: `${data.toEmail.join(",")}`, // list of receivers
        subject: data.subject, // Subject line       
        html: data.body, // html body
    });

    return info
}

module.exports = {
    sendMail
}


after sending mail when user will click on the link then he will redirected to reset password form with token string attached in the form.

After submitting of new password we can check that token from reset_password table and get the user_id from that table and then update the new password for that user.

I will update this post for remain part if get time.

If you face any problem with this post or if you want some change then definitely can reach out to me

comments powered by Disqus