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.
App overview :
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
Next, we initialize the Node.js App with a package.json file:
$ npm init --yes
We need to install necessary modules: express, mongoose, nodemailer, joi and dotenv.
$ 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 :
{
"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 :
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 :
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 :
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 :
//....
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 :
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 :
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 :
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 :
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
//....
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 :)
Top comments (8)
For everyone confused as to what the node env values should be:
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.
can you share .env file?
DB = // mongodb url
HOST = // email host
USER = // email id
PASS = // email password
SERVICE = // email service
BASE_URL = 'http://localhost:8080/api'
create your own one
or use the values instead
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 usingprocess.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.
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.
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
Where's this BASE_URL coming from, seems it's on your dotenv file