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
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