How to verify user email in node.js ?
Tags: node, javascript, mongodb
Published: 2021-05-10 15:01:36

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.

{% youtube 3eN5ZghVLAg %}

[Project Github link](https://github.com/sk-Jahangeer/node-mongo-email-verify)

**App overview :**
project structure
![project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr6i2ryqwro12z0i8xgz.png)

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 :
```javascript
$ mkdir node-mongo-email-verify
$ cd node-mongo-email-verify
```
Next, we initialize the Node.js App with a package.json file:
```javascript 
$ npm init --yes
```
We need to install necessary modules: express, mongoose, nodemailer, joi and dotenv.
```javascript
$ npm install express mongoose nodemailer joi dotenv
```
**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 :
```json
{
  "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"
  }
}
```

**Setup Express Web Server**
In the root folder, let's create new index.js file :
```javascript
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}...`));
```
Now let's run  the app with command:  node index.js

**Configure Environment Variables :**
In the root folder, let's create new .env file :
```javascript
DB = // mongodb url
HOST = // email host
USER = // email id
PASS = // email password
SERVICE = // email service
BASE_URL = 'http://localhost:8080/api'
```

**Configure MongoDB database :**
In the root folder, let's create new db.js file :
```javascript
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.");
  }
};
```
import db.js to index.js and call it :
```javascript
//....
const connection = require("./db");
const express = require("express");
//.....

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

app.use(express.json());
//....
```

**Define The Model :**
In model folder, create user.js file like this :
```javascript
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,
};
```
In models folder create token.js file like this :
```javascript
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;
```

**Configure The Email Transporter :**
In the utils folder, create email file like this :
```javascript
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;
```

**Define The Routes :**
In the routes folder, create users.js file :
```javascript
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;
```
import users route to index.js
```javascript
//....
const user = require("./routes/users");
const connection = require("./db");
//.....

app.use(express.json());
app.use("/api/user", user);
//....
```
That's it check the APIs in postman :)