Nodejs and redis

Node.js and Redis tutorial – Building Email verification system

Node.js and Redis are a very powerful combination to yield a high-performance result. In the previous tutorial, we have covered the installation and useful Redis commands. In this tutorial, we will take one step ahead and build a useful real-time application.

We have already covered Redis as session store in one of our tutorial. If you haven’t read that, click here to read that awesome article.

YOUTUBE DEMO DOWNLOAD

How email verification system works

Email verification is a very common feature which I believe every user-base site maintains. This is how it works.

When you signup for an email, system generates a link which contains information such as unique generated code along with other details to identify the request.

Upon clicking on the link, the system decodes those code and perform the identification. We will do same and will store the uniquely generated code in Redis with a timer for say 15 minutes. After 15 minutes, Redis will delete those keys and link should not work.

Prerequistics

In order to make the project work you need to integrate the email SMTP and have Node.js and Redis installed in your system. You can use Google SMTP as well which I have covered here in detail.

For the sake of simplicity in sending emails, I am going to use Mandrill – transactional email system by Mailchimp. I will cover configuring that in detail in next tutorial because there are many steps to get the API key of Mandrill.

Project structure

Here is our folder structure.

|
|--node_modules
|--index.html
|--app.js
|--package.json
|

Package.json

Here is our package.json file.

{
  "name": "email-node-redis",
  "version": "1.0.0",
  "dependencies": {
    "async": "2.0.0-rc.5",
    "body-parser": "1.15.1",
    "express": "4.5.1",
    "nodemailer": "latest",
    "nodemailer-mandrill-transport": "1.1.0",
    "redis": "2.6.0-2"
  }
}

To install dependencies, switch to project directory using Terminal or command prompt and run

npm install

Node Server

Make sure you have installed Redis and running it at default port. If you are confused about Installation about Redis and want to learn it’s basics, please visit this tutorial first.

Sending an email

First, we need to able to send an email along with the link. In order to achieve that, we will create an Express router and send an email using Mandrill.

Server.js
var express = require('express');
var bodyParser = require('body-parser');
var nodemailer = require("nodemailer");
var redis = require('redis');
var redisClient = redis.createClient(); // default setting.
var mandrillTransport = require('nodemailer-mandrill-transport');
var async = require('async');
var app = express();

/*
    * Here we are configuring our SMTP Server details.
    * STMP is mail server which is responsible for sending and receiving email.
  * We are using Mandrill here.
*/


var smtpTransport = nodemailer.createTransport(mandrillTransport({
    auth: {
      apiKey : ''
    }
}));
/*------------------SMTP Over-----------------------------*/

/*------------------Routing Started ------------------------*/
var host = "localhost:3000";
app.use(bodyParser.urlencoded({"extended" : false}));

/* Sending index.html to browser */

app.get('/',function(req,res){
    res.sendfile('index.html');
});

app.post('/send',function(req,res) {
  console.log(req.body.to);
  async.waterfall([
    // Check if email already exists.
    // format to store in Redis is {email : unique key}
    function(callback) {
      redisClient.exists(req.body.to,function(err,reply) {
        if(err) {
          return callback(true,"Error in redis");
        }
        if(reply === 1) {
          return callback(true,"Email already requested");
        }
        callback(null);
      });
    },
    function(callback) {
      // Generating random string.
      let rand = Math.floor((Math.random() * 100) + 54);
      let encodedMail = new Buffer(req.body.to).toString('base64');
      let link="http://"+req.get('host')+"/verify?mail="+encodedMail+"&id="+rand;
      let mailOptions={
        from : '[email protected]',
        to : req.body.to,
        subject : "Please confirm your Email account",
        html : "Hello,<br> Please Click on the link to verify your email.<br><a href="+link+">Click here to verify</a>"
      };
      callback(null,mailOptions,rand);
    },
    function(mailData,secretKey,callback) {
      console.log(mailData);
      // Sending email using Mandrill.
      smtpTransport.sendMail(mailData, function(error, response){
         if(error){
          console.log(error);
          return callback(true,"Error in sending email");
       }
        console.log("Message sent: " + JSON.stringify(response));
        // Adding hash key.
        redisClient.set(req.body.to,secretKey);
        redisClient.expire(req.body.to,600); // setting expiry for 10 minutes.
        callback(null,"Email sent Successfully");
    });
    }
  ],function(err,data) {
    console.log(err,data);
    res.json({error : err === null ? false : true, data : data});
  });
});

app.get('/verify',function(req,res) {
  /* In next section*/
});
/*--------------------Routing Over----------------------------*/

app.listen(3000,function(){
    console.log("Express Started on Port 3000");
});

We are using Async waterfall to structure our code. In the first function, we are checking whether the key with the same email is present in Redis already or not ? If not this is new user hence we will send an Email otherwise email is already sent but not yet verified.

In the second function, we are generating a unique random string and we are also encoding email address using Base-64. After this, we are using Mandrill transport to send an Email and once Email is sent, we are adding the entry in Redis in the following format.

{
  "email-address" : "random strings"
}

We are also setting 10 minutes as an expiry time to the key so that it will get automatically deleted if no action is been taken by the user.

Performing verfication

In the link present in Email, once the user clicks on that will be redirected to our hostname provided in the code. In the link, there is two information present in an encoded format.

  1. Encoded email address.
  2. Encoded unique code.

We need to decode these two, check in Redis server and if found return the proper response. To do so, we will use another route in Express.

Server.js
var express = require('express');
var bodyParser = require('body-parser');
var nodemailer = require("nodemailer");
var redis = require('redis');
var redisClient = redis.createClient(); // default setting.
var mandrillTransport = require('nodemailer-mandrill-transport');
var async = require('async');
var app = express();

/*
    * Here we are configuring our SMTP Server details.
    * STMP is mail server which is responsible for sending and receiving email.
  * We are using Mandrill here.
*/


var smtpTransport = nodemailer.createTransport(mandrillTransport({
    auth: {
      apiKey : ''
    }
}));
/*------------------SMTP Over-----------------------------*/

/*------------------Routing Started ------------------------*/
var host = "localhost:3000";
app.use(bodyParser.urlencoded({"extended" : false}));

app.get('/',function(req,res){
    res.sendfile('index.html');
});

app.post('/send',function(req,res) {
  // Refer above section.
});

app.get('/verify',function(req,res) {
  if((req.protocol+"://"+req.get('host')) === ("http://"+host)) {
    async.waterfall([
      function(callback) {
        let decodedMail = new Buffer(req.query.mail, 'base64').toString('ascii');
        redisClient.get(decodedMail,function(err,reply) {
          if(err) {
            return callback(true,"Error in redis");
          }
          if(reply === null) {
            return callback(true,"Invalid email address");
          }
          callback(null,decodedMail,reply);
        });
      },
      function(key,redisData,callback) {
        if(redisData === req.query.id) {
          redisClient.del(key,function(err,reply) {
            if(err) {
              return callback(true,"Error in redis");
            }
            if(reply !== 1) {
              return callback(true,"Issue in redis");
            }
            callback(null,"Email is verified");
          });
        } else {
          return callback(true,"Invalid token");
        }
      }
    ],function(err,data) {
      res.send(data);
    });
  } else {
    res.end("<h1>Request is from unknown source");
  }
});

/*--------------------Routing Over----------------------------*/

app.listen(3000,function(){
    console.log("Express Started on Port 3000");
});

Once verification link is clicked, we will first check whether the hostname matches or not. On the success of that, we will decode email address and check in Redis for the Key. If the key is present, we will compare them, if not present then we will consider it an invalid link.

Front-end app

We are using simple HTML and jQuery to design our front-end. We are calling our API from the client end using $.post() jQuery method. Here is our end output.

Nodejs and redis tutorial

Here is our code.

index.html
<html>
<head>
<title>Node.JS Email application</title>
<body>
<div id="container">
<h1>Email-verification System in Node.js</h1>
<input type="text" id="to" placeholder="Enter E-mail which you want to verify"><br>
<button id="send_email">Send Email</button><br>
<span id="message"></span>
</div>
</body>
</html>

Here is our JS.

index.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    var from,to,subject,text;
    $("#send_email").click(function(){
        to = $("#to").val();
        $("#message").text("Sending E-mail...Please wait");
        $.post("/send",{to:to},function(response) {
            if(response.error) {
                  $("#message").empty().html("Error - " + response.data);
            } else {
              $("#message").empty().html("<p>Email is been sent at "+to+" . Please check inbox !</p>");
            }
        });
    });
});
</script>

Here is our CSS.

index.html
<style>
#container {
    margin-left:400px;
    margin-top:50px;
}
#to,#subject,#content {
    font-family: "Segoe UI";
    font-size:18px;
    width:530px;
}
h1 {
    font-family: "Segoe UI";
    font-size:40px;
    color: #3b5998;
}
p {
    color:green;
}
#send_email {
    font-size:15px;
    font-family: "Segoe UI";
}
#message {
    font-size:18px;
}
</style>

Running the code

Run your redis in background using following command.

redis-server &

Switch to the project directory, run the following command to lift the Node web server.

node Server.js

Point your browser to localhost:3000 to view the app. Type an email to receive a verification link and check the entry in Redis. Here is the demo video.

Conclusion

This post summarizes the Node.js and Redis tutorial along with a fully fledged working example. We have learned useful Redis commands in our first post and here we have covered the Email verification system as an example. Redis is no doubt very fast, durable and useful NoSQL database system for any web application.

Shahid (UnixRoot) Shaikh

Hey there, This is Shahid, an Engineer and Blogger from Bombay. I am also an Author and i wrote a programming book on Sails.js, MVC framework for Node.js.

Related Posts

9 Comments

  1. I’m not sure but passing “host” that way `let link=”http://”+req.get(‘host’)+”/verify?mail=”+encodedMail+”&id=”+rand;` may be security issue! 🙂

  2. Why must you verify that the hostname is correct? How would the user hit the server if they didn’t use the correct hostname?

      1. I’m legitimately asking. I don’t see how if you send somebody a link they will be able to change the hostname *and* still be able to hit your server. It almost seems like an unnecessary precaution but I want to make sure I’m not missing something more important.

        Thank you for taking the time to respond.

        1. You see I wasn’t sure at the time of writing code. I know I can do this (changing the hostname with same key value pair) hence wrote a validation. Can’t think of any practical use case but having something is better than nothing, so upto you 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *