Using Redis to handle Session in Node.js

We have covered session management in ExpressJs using global variable technique which of course will not work in case of shared server or concurrent execution of http requests which is most familiar production scenario.

Codeforgeek readers requested to provide solution for these issue and the optimal one is to use external session storage which is not dependent on application requests, answer is Redis cause this is the light weight and easy to use NoSQL database.

In this tutorial i am going to explain how to design and code session oriented express web applications by using Redis as external session storage.

DOWNLOAD

To get familiar with Session handling in ExpressJS I recommend to read our first article here.

Getting started with Redis :

If you have already installed Redis please go to next section. For those of you who are not familiar with Redis here is a little introduction.

Redis is key value pair cache and store. it is also referred as data structure server cause keys can contain List, Hash, sets, sorted sets etc.

Redis is fast cause it work in memory data set i.e it by default stores data in your memory than disk and if you from CS background you very well know CRUD operation on memory is way faster than disk, so is Redis.

if you restart Redis or shut it down you may lose all data unless you enable option to dump those data in hard disk. Be careful !

Installation:

1 : On mac

On Mac if you have brew install then just open up your terminal and type

brew install redis

Make sure you have command line tools installed cause it need GCC to compile it.

If you don’t have brew then please install it. It’s awesome !

2 : On ubuntu

Run following command on Ubuntu and rest will be done

sudo apt-get install redis-server

3 : On Windows

Well Redis does not support Windows ! Hard luck.

Basic REDIS command :

I am going to mention those command which i need to go with this tutorial. For detailed information please visit the nice demo built by awesome Redis team to make you pro in Redis.

1 : Starting Redis server.

Run this command on terminal.

redis-server &

2 : Open Redis CLI too.

Run this command on terminal.

redis-cli

3 : List all Keys.
Run this command on terminal.

KEYS *

4 : Retrieve information regarding particular key.
Run this command on terminal.

GET <key name>

Once you have installed Redis, by running first command you should something like this.
Redis start sceeen

Express session with Redis

To add support of Redis you have to use Redis client and connect-redis. Create express-session and pass it to connect-redis object as parameter. This will initialize it.

Then in session middle ware, pass the Redis store information such as host, port and other required parameters.

Here is sample express code with Redis support. Have a look.

Express Session using Redis :
var express = require('express');
var redis   = require("redis");
var session = require('express-session');
var redisStore = require('connect-redis')(session);
var bodyParser = require('body-parser');
var client  = redis.createClient();
var app = express();

app.set('views', __dirname + '/views');
app.engine('html', require('ejs').renderFile);

app.use(session({
    secret: 'ssshhhhh',
    // create new redis store.
    store: new redisStore({ host: 'localhost', port: 6379, client: client,ttl :  260}),
    saveUninitialized: false,
    resave: false
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.get('/',function(req,res){ 
    // create new session object.
    if(req.session.key) {
        // if email key is sent redirect.
        res.redirect('/admin');
    } else {
        // else go to home page.
        res.render('index.html');
    }
});

app.post('/login',function(req,res){
    // when user login set the key to redis.
    req.session.key=req.body.email;
    res.end('done');
});

app.get('/logout',function(req,res){
    req.session.destroy(function(err){
        if(err){
            console.log(err);
        } else {
            res.redirect('/');
        }
    });
});

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

Notice the code where we are initiating the session. Have a look.

app.use(session({
    secret: 'ssshhhhh',
    // create new redis store.
    store: new redisStore({ host: 'localhost', port: 6379, client: client}),
    saveUninitialized: false,
    resave: false
}));

If Redis server running, then this is default configuration. Once you have configured it. Store your session key in the way we were doing in previous example.

req.session.key_name = value to set
// this will be set to redis, value may contain User ID, email or any information which you need across your application.

Fetch the information from redis session key.

req.session.key["keyname"]

Our project:

To demonstrate this i have developed web application which will allow you to register and login and post some status. It’s simple but it will demonstrate you how to handle session using external storage.

Create project folder and copy these code to package.json.

Our package.json :

package.json
{
  "name": "Node-Session-Redis",
  "version": "0.0.1",
  "scripts": {
    "start": "node ./bin"
  },
  "dependencies": {
    "async": "^1.2.1",
    "body-parser": "^1.13.0",
    "connect-redis": "^2.3.0",
    "cookie-parser": "^1.3.5",
    "ejs": "^2.3.1",
    "express": "^4.12.4",
    "express-session": "^1.11.3",
    "mysql": "^2.7.0",
    "redis": "^0.12.1"
  }
}

Install dependencies by running following command.

npm install

Our database:

Once completed let’s design simple database to support our application. Here is the diagram.
database diagram

Database is simple and straight, please create database in MySQL and run following DDL queries.

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';

CREATE SCHEMA IF NOT EXISTS `redis_demo` DEFAULT CHARACTER SET latin1 ;
USE `redis_demo` ;

-- -----------------------------------------------------
-- Table `redis_demo`.`user_login`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `redis_demo`.`user_login` (
  `user_id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '',
  `user_email` VARCHAR(50) NOT NULL COMMENT '',
  `user_password` VARCHAR(50) NOT NULL COMMENT '',
  `user_name` VARCHAR(50) NOT NULL COMMENT '',
  PRIMARY KEY (`user_id`)  COMMENT '',
  UNIQUE INDEX `user_email` (`user_email` ASC)  COMMENT '')
ENGINE = InnoDB
AUTO_INCREMENT = 7
DEFAULT CHARACTER SET = latin1;

-- -----------------------------------------------------
-- Table `redis_demo`.`user_status`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `redis_demo`.`user_status` (
  `user_id` INT(11) NOT NULL COMMENT '',
  `user_status` TEXT NOT NULL COMMENT '',
  `created_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '',
  INDEX `user_id` (`user_id` ASC)  COMMENT '',
  CONSTRAINT `user_status_ibfk_1`
    FOREIGN KEY (`user_id`)
    REFERENCES `redis_demo`.`user_login` (`user_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Our Server code

Server file contains application routes, database support and Redis session support. We will first create connect to database and initialize Redis then according to Routes particular action will happen.

/bin/index.js
/**
  Loading all dependencies.
**/

var express         =     require("express");
var redis           =     require("redis");
var mysql           =     require("mysql");
var session         =     require('express-session');
var redisStore      =     require('connect-redis')(session);
var bodyParser      =     require('body-parser');
var cookieParser    =     require('cookie-parser');
var path            =     require("path");
var async           =     require("async");
var client          =     redis.createClient();
var app             =     express();
var router          =     express.Router();

// Always use MySQL pooling.
// Helpful for multiple connections.

var pool    =   mysql.createPool({
    connectionLimit : 100,
    host     : 'localhost',
    user     : 'root',
    password : '',
    database : 'redis_demo',
    debug    :  false
});

app.set('views', path.join(__dirname,'../','views'));
app.engine('html', require('ejs').renderFile);

// IMPORTANT
// Here we tell Express to use Redis as session store.
// We pass Redis credentials and port information.
// And express does the rest !

app.use(session({
    secret: 'ssshhhhh',
    store: new redisStore({ host: 'localhost', port: 6379, client: client,ttl :  260}),
    saveUninitialized: false,
    resave: false
}));
app.use(cookieParser("secretSign#143_!223"));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

// This is an important function.
// This function does the database handling task.
// We also use async here for control flow.

function handle_database(req,type,callback) {
   async.waterfall([
    function(callback) {
        pool.getConnection(function(err,connection){
          if(err) {
                   // if there is error, stop right away.
                   // This will stop the async code execution and goes to last function.
            callback(true);
          } else {
            callback(null,connection);
          }
        });
    },
    function(connection,callback) {
      var SQLquery;
      switch(type) {
       case "login" :
        SQLquery = "SELECT * from user_login WHERE user_email='"+req.body.user_email+"' AND `user_password`='"+req.body.user_password+"'";
        break;
            case "checkEmail" :
             SQLquery = "SELECT * from user_login WHERE user_email='"+req.body.user_email+"'";
            break;
        case "register" :
        SQLquery = "INSERT into user_login(user_email,user_password,user_name) VALUES ('"+req.body.user_email+"','"+req.body.user_password+"','"+req.body.user_name+"')";
        break;
        case "addStatus" :
        SQLquery = "INSERT into user_status(user_id,user_status) VALUES ("+req.session.key["user_id"]+",'"+req.body.status+"')";
        break;
        case "getStatus" :
        SQLquery = "SELECT * FROM user_status WHERE user_id="+req.session.key["user_id"];
        break;
        default :
        break;
    }
    callback(null,connection,SQLquery);
    },
    function(connection,SQLquery,callback) {
       connection.query(SQLquery,function(err,rows){
           connection.release();
        if(!err) {
            if(type === "login") {
              callback(rows.length === 0 ? false : rows[0]);
            } else if(type === "getStatus") {
                          callback(rows.length === 0 ? false : rows);
                        } else if(type === "checkEmail") {
                          callback(rows.length === 0 ? false : true);
                        } else {
                      callback(false);
            }
        } else {
             // if there is error, stop right away.
            // This will stop the async code execution and goes to last function.
            callback(true);
         }
    });
       }],
       function(result){
      // This function gets call after every async task finished.
      if(typeof(result) === "boolean" && result === true) {
        callback(null);
      } else {
        callback(result);
      }
    });
}

/**
    --- Router Code begins here.
**/


router.get('/',function(req,res){
    res.render('index.html');
});

router.post('/login',function(req,res){
    handle_database(req,"login",function(response){
        if(response === null) {
            res.json({"error" : "true","message" : "Database error occured"});
        } else {
            if(!response) {
              res.json({
                             "error" : "true",
                             "message" : "Login failed ! Please register"
                           });
            } else {
               req.session.key = response;
                   res.json({"error" : false,"message" : "Login success."});
            }
        }
    });
});

router.get('/home',function(req,res){
    if(req.session.key) {
        res.render("home.html",{ email : req.session.key["user_name"]});
    } else {
        res.redirect("/");
    }
});

router.get("/fetchStatus",function(req,res){
  if(req.session.key) {
    handle_database(req,"getStatus",function(response){
      if(!response) {
        res.json({"error" : false, "message" : "There is no status to show."});
      } else {
        res.json({"error" : false, "message" : response});
      }
    });
  } else {
    res.json({"error" : true, "message" : "Please login first."});
  }
});

router.post("/addStatus",function(req,res){
    if(req.session.key) {
      handle_database(req,"addStatus",function(response){
        if(!response) {
          res.json({"error" : false, "message" : "Status is added."});
        } else {
          res.json({"error" : false, "message" : "Error while adding Status"});
        }
      });
    } else {
      res.json({"error" : true, "message" : "Please login first."});
    }
});

router.post("/register",function(req,res){
    handle_database(req,"checkEmail",function(response){
      if(response === null) {
        res.json({"error" : true, "message" : "This email is already present"});
      } else {
        handle_database(req,"register",function(response){
          if(response === null) {
            res.json({"error" : true , "message" : "Error while adding user."});
          } else {
            res.json({"error" : false, "message" : "Registered successfully."});
          }
        });
      }
    });
});

router.get('/logout',function(req,res){
    if(req.session.key) {
    req.session.destroy(function(){
      res.redirect('/');
    });
    } else {
        res.redirect('/');
    }
});

app.use('/',router);

app.listen(3000,function(){
    console.log("I am running at 3000");
});

Explanation:

When user provides login credentials then we are checking it against our database. If it’s successful then we are setting database response in our Redis key store. This is where Session has been started.

Now as soon as User go to homepage, we are validating the session key and if it is there, then retrieving the user id from it to fire further MySQL queries.

When user click on Logout, we are calling req.session.destroy() function which in turn deletes the key from Redis and ends the session.

Views ( Index.html and Home.html )

Here is our home page code.

/view/index.html
<html>
<head>
<title>Home</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
$(document).ready(function(){
    $("#username").hide();
    $('#login-submit').click(function(e){
      if($(this).attr('value') === 'Register') {
        $.post("http://localhost:3000/register",{
               user_name : $("#username").val(),
               user_email : $("#useremail").val(),
               user_password : $("#password").val()
             },function(data){
        if(data.error) {
            alert(data.message);
        } else {
            $("#username").hide();
            $("#login-submit").prop('value','Log in');
        }
    });
    } else {
        $.post("http://localhost:3000/login",{
                   user_email : $("#useremail").val(),
                   user_password : $("#password").val()
                   },function(data){
            if(!data.error) {
                window.location.href = "/home";
            } else {
                alert(data.message);
            }
        });
    }
    });
    $("#reg").click(function(event){
        $("#username").show('slow');
        $("#login-submit").prop('value','Register');
        event.preventDefault();
    });
});
</script>
    </head>
    <body>
    <nav class="navbar navbar-default navbar-fixed-top">
    <div class="navbar-header">
    <a class="navbar-brand" href="#">
        <p>Redis session demo</p>
    </a>
    </div>
  <div class="container">
    <p class="navbar-text navbar-right">Please sign in</a></p>
  </div>
</nav>
<div class="form-group" style="margin-top: 100px; width : 400px; margin-left : 50px;">
    <input type="text" id="username" placeholder="Name" class="form-control"><br>
    <input type="text" id="useremail" placeholder="Email" class="form-control"><br>
    <input type="password" id="password" placeholder="Password" class="form-control"><br>
    <input type="button" id="login-submit" value="Log In" class="btn btn-primary">&nbsp;<a href="" id="reg">Sign up here </a>
    </div>
    </body>
</html>

Here is how it looks.

Home page - Redis session

/view/home.html
<html>
<head>
<title>Home</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript">
    $(document).ready(function(){
        $.get("http://localhost:3000/fetchStatus",function(res){
            $.each(res.message,function(index,value) {
                $("#showStatus").append('You have posted <br> <p>'+value.user_status+'</p><hr>');
            });
        });
    $("#addNewStatus").click(function(e){
        e.preventDefault();
            if($("#statusbox").text !== "") {
                $.post("/addStatus",
                                  { status : $("#statusbox").val() },
                                   function(res){
                    if(!res.error) {
                        alert(res.message);
                    }
                })
            }
        });
});
</script>
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-brand" href="#">
<p>Redis session demo</p>
</a></div><div class="container">
<p class="navbar-text navbar-right">Hi you are login as <b><%= email %></b> (<a href="/logout/">Logout</a>)</p>
</div>
</nav>
<div style="margin-top:100px;margin-left:50px;width:400px">
<textarea rows="10" cols="5" id="statusbox" class="form-control"></textarea><br>
<input type="submit" id="addNewStatus" value="Post" class="btn btn-primary"><br>
<div id="showStatus" style="border : 2px grey; border-radius : 4px;">
</div>
</div>
</body>
</html>

Here is how it looks.

Home page - redis session

How to run:

Download code from Github and extract the file. Make sure you have installed Redis and created database.

If your database name and MySQL password is different then please update it on bin/index.js file.

Once done type npm start on terminal to start the project.

Start the script

Now open your browser and go to localhost:3000 to view the app. Register with your email id and password and then Login with same. After login, have a look to Redis using commands mentioned above.

Redis key set

Now logout from System and check for same key. It should not be there.

After logout

This is it. Express session with Redis is working.

Conclusion:

Redis is one of the popular key storage database system. Using it to store session is not only beneficial in production environment but also it helps to improve performance of system.

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

33 Comments

  1. How I can check if a session has expired ?
    If you run a custom function expired

    I’m Working with Node.js , Express, Express- session and Connect- mongo
    Thanks for your time

    1. That’s a good question.

      If you notice the code, we are checking session using req.session.key. If it is not there then that means there either no session set or session is expired.

  2. Great post!
    I can recommend a non-blocking client library: https://github.com/mscdex/node-mariasql which is mysql compatible. Since i am no Oracle (or mongoDB) fan I have switched to MariaDB.

    When it comes to session storage in Express.js it is really problematic. I have tried memory, filestorage and now Knex with a MariaDB connection (npm express-session). All the time I get “Error: MySQL server has gone away at Error (native) express.js” and the Node server crashes. So now I am looking for a simple solution to use either Firebase or Redis.

    1. Yes. I too use mariaDB often for production.

      However for handling Session, it is not recommended at all. You can use Redis like i have explained in above post or can use MongoDB as well. However, i will recommend Redis for it.

      1. Yep, using Redis now. Stable and very fast at least on my virtual dev server! Good instructions!

  3. On Ubuntu 14.* you need to add a repo and then: sudo apt-get install redis-server or download the code from the source then build and test it, which is the best solution.

      1. Ok, I did not try without adding repo. Not trying to be a besserwisser but it is sudo apt-get install redis-server and not sudo apt-get install redis (instruction, 2 : On ubuntu).

  4. when i store data in redis using provided codebase I am unable to view it in redis-cli why so and is it compulsory to use req.session.key the key could any name like userresponse right for instance
    req.session.userresponse please suggest !

  5. hi

    app.post(‘/muser/validate’, function(req, res) {

    sess=req.session.key
    sess.finalResult=”some json object”;
    it is gving error “Cannot set property ‘finalResult’ of undefined”

  6. Hi,

    CAn we store complex json object in redis for session management purpose
    sample json
    [
    {
    “_id”: “56cd5eda460319cbca9021de”,
    “UName”: “matt”,
    “Username”: “matt”,
    “isactive”: “true”,
    “Rolename”: “superadmin”,
    “Access”: [
    {
    “Modulename”: [
    “sales”,
    ],
    “Accestype”: [
    “View”
    ]
    },
    {
    “Modulename”: [
    “muser”,
    “mrole”,
    “modulename”
    ],
    “Accestype”: [
    “View”,
    “manage”,
    ]
    }
    ]

  7. hi hi. Im newbie in nodejs and have a question about this code.
    handle_database, all time make new connection? Or it initialize once connect to db and after use it over and over.
    Coz as I know it is bad, to make all time new connection.
    Sorry for my question, I just want to know how to do better. 🙂

    1. Hi,

      Even though we are calling a function to get a new connection, all we are getting is the connection we just released to the pool. So as long as you are using and releasing connection properly, there is no issue.

  8. I don’t understand “req.session.key”,this “key” means I can set anything which I like, such as req.session.aaa=’ccc’ , is that correct ?
    so if I set a variable as a key like req.session[myvariable] = otherVariable;
    how can I search this session key?

Leave a Reply

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