DEV Community

Cover image for How to verify user email in node.js ?
CyberWolves
CyberWolves

Posted on • Edited on

How to verify user email in node.js ?

Email verification is crucial feature for every website. It will help us from spam users. It is my first blog, i will try my level best. So let's start coding.

Project Github link

App overview :
project structure
project structure

The following table shows overview of the Rest APIs that be exported:

Methods Urls Actions
POST api/user/ create user and send email
GET api/user/verify/:id/:token verify link sent by email

Create Node.js App
first, we create a folder :

$ mkdir node-mongo-email-verify
$ cd node-mongo-email-verify
Enter fullscreen mode Exit fullscreen mode

Next, we initialize the Node.js App with a package.json file:

$ npm init --yes
Enter fullscreen mode Exit fullscreen mode

We need to install necessary modules: express, mongoose, nodemailer, joi and dotenv.

$ npm install express mongoose nodemailer joi dotenv
Enter fullscreen mode Exit fullscreen mode

Express : Express is minimal and flexible Node.js web applicaton framework.
Mongoose : Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js.
Nodemailer : Nodemailer allow us to send email.
Joi : Joi is an object schema description language and validator for javascript objects.
dotenv : It loads environment variables from a .env file.

The package.json file should look like this :

{
  "name": "node-mongo-email-verify",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^9.0.0",
    "express": "^4.17.1",
    "joi": "^17.4.0",
    "mongoose": "^5.12.7",
    "nodemailer": "^6.6.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup Express Web Server
In the root folder, let's create new index.js file :

require("dotenv").config();
const express = require("express");
const app = express();

app.use(express.json());

const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening on port ${port}...`));
Enter fullscreen mode Exit fullscreen mode

Now let's run the app with command: node index.js

Configure Environment Variables :
In the root folder, let's create new .env file :

DB = // mongodb url
HOST = // email host
USER = // email id
PASS = // email password
SERVICE = // email service
BASE_URL = 'http://localhost:8080/api'
Enter fullscreen mode Exit fullscreen mode

Configure MongoDB database :
In the root folder, let's create new db.js file :

const mongoose = require("mongoose");

module.exports = async function connection() {
  try {
    const connectionParams = {
      useNewUrlParser: true,
      useCreateIndex: true,
      useUnifiedTopology: true,
    };
    await mongoose.connect(process.env.DB, connectionParams);
    console.log("connected to database.");
  } catch (error) {
    console.log(error, "could not connect to database.");
  }
};
Enter fullscreen mode Exit fullscreen mode

import db.js to index.js and call it :

//....
const connection = require("./db");
const express = require("express");
//.....

(async () => await connection())();

app.use(express.json());
//....
Enter fullscreen mode Exit fullscreen mode

Define The Model :
In model folder, create user.js file like this :

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

const userSchema = new Schema({
  name: {
    type: String,
    min: 3,
    max: 255,
    required: true,
  },
  email: {
    type: String,
    required: true,
  },
  verified: {
    type: Boolean,
    default: false,
  },
});

const User = mongoose.model("user", userSchema);

const validate = (user) => {
  const schema = Joi.object({
    name: Joi.string().min(3).max(255).required(),
    email: Joi.string().email().required(),
  });
  return schema.validate(user);
};

module.exports = {
  User,
  validate,
};
Enter fullscreen mode Exit fullscreen mode

In models folder create token.js file like this :

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

const tokenSchema = new Schema({
  userId: {
    type: Schema.Types.ObjectId,
    ref: "user",
    required: true,
  },
  token: {
    type: String,
    required: true,
  },
});

const Token = mongoose.model("token", tokenSchema);

module.exports = Token;
Enter fullscreen mode Exit fullscreen mode

Configure The Email Transporter :
In the utils folder, create email file like this :

const nodemailer = require("nodemailer");

const sendEmail = async (email, subject, text) => {
  try {
    const transporter = nodemailer.createTransport({
      host: process.env.HOST,
      service: process.env.SERVICE,
      port: 587,
      secure: true,
      auth: {
        user: process.env.USER,
        pass: process.env.PASS,
      },
    });

    await transporter.sendMail({
      from: process.env.USER,
      to: email,
      subject: subject,
      text: text,
    });
    console.log("email sent sucessfully");
  } catch (error) {
    console.log("email not sent");
    console.log(error);
  }
};

module.exports = sendEmail;
Enter fullscreen mode Exit fullscreen mode

Define The Routes :
In the routes folder, create users.js file :

const sendEmail = require("../utils/email");
const Token = require("../models/token");
const { User, validate } = require("../models/user");
const crypto = import("crypto");
const express = require("express");
const router = express.Router();

router.post("/", async (req, res) => {
  try {
    const { error } = validate(req.body);
    if (error) return res.status(400).send(error.details[0].message);

    let user = await User.findOne({ email: req.body.email });
    if (user)
      return res.status(400).send("User with given email already exist!");

    user = await new User({
      name: req.body.name,
      email: req.body.email,
    }).save();

    let token = await new Token({
      userId: user._id,
      token: crypto.randomBytes(32).toString("hex"),
    }).save();

    const message = `${process.env.BASE_URL}/user/verify/${user.id}/${token.token}`;
    await sendEmail(user.email, "Verify Email", message);

    res.send("An Email sent to your account please verify");
  } catch (error) {
    res.status(400).send("An error occured");
  }
});

router.get("/verify/:id/:token", async (req, res) => {
  try {
    const user = await User.findOne({ _id: req.params.id });
    if (!user) return res.status(400).send("Invalid link");

    const token = await Token.findOne({
      userId: user._id,
      token: req.params.token,
    });
    if (!token) return res.status(400).send("Invalid link");

    await User.updateOne({ _id: user._id, verified: true });
    await Token.findByIdAndRemove(token._id);

    res.send("email verified sucessfully");
  } catch (error) {
    res.status(400).send("An error occured");
  }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

import users route to index.js

//....
const user = require("./routes/users");
const connection = require("./db");
//.....

app.use(express.json());
app.use("/api/user", user);
//....
Enter fullscreen mode Exit fullscreen mode

That's it check the APIs in postman :)

Top comments (8)

Collapse
 
catheryn profile image
Esther

For everyone confused as to what the node env values should be:

const transporter = nodemailer.createTransport({
      host: process.env.HOST, //'smtp.gmail.com' - I used my gmail account
      service: process.env.SERVICE, //I ignored this
      port: 587, // 465 - because I used gmail
      secure: true, - leave as true
      auth: {
          user: process.env.USER, // my gmail email
          pass: process.env.PASS, // I created an app on my gmail account and used the password
      },
});
Enter fullscreen mode Exit fullscreen mode

Use this link to create an app password support.google.com/accounts/answer...

Note: if you do not have 2FA authentication enabled, you will not see this option.

Let me know if you have any issues.

Collapse
 
damladebug profile image
damla yaÅŸar

can you share .env file?

Collapse
 
khaled17 profile image
khaled-17

DB = // mongodb url
HOST = // email host
USER = // email id
PASS = // email password
SERVICE = // email service
BASE_URL = 'http://localhost:8080/api'

Collapse
 
mahmoudgalal profile image
Mahmoud Galal

create your own one
or use the values instead

Collapse
 
faithcyril profile image
FaithCyril-star • Edited

process.env.USER is actually a system environment variable that holds the name of the user that is currently logged in to the operating system. So if you are using process.env.USER in your Node.js code, it will automatically pick up the value of this system environment variable, rather than the value you have set in your .env file.

I run into this problem and got stuck but upon logging the value of the env variables I realised it was my OS username and not the email address I passed in the env file.

correct it to EMAIL = 'your-email-address' in your env file to save yourself from headache.

Collapse
 
kokole12 profile image
Kokole Ismail

Am seeing your saving users to the database before verification and not after the verification. the logic is supposed to be save user to the database after they verify their accounts or delete the details from the database in case the account inst verified correct me if am wrong here.

Collapse
 
steve-lebleu profile image
Steve Lebleu

Thanks for sharing ! On the road, there is a light package to put flexibility and simplicity when you deal with email sending in node.js: github.com/steve-lebleu/cliam

Collapse
 
anozieluciana profile image
Anozieluciana

Where's this BASE_URL coming from, seems it's on your dotenv file