Node.js Session Management Using Express Sessions, Redis, and Passport - Part 1
Tags: node, express, redis, session
Published: 2019-07-19 15:22:05

Recently, I set out to create a demo application for my talk at [Redis Day NYC](https://events.redislabs.com/redis-day-nyc/redis-day-nyc-2019/) that illustrates how session management works in a Node.js/Express web app, using Redis as the session store and then adds authentication on top of all that. Understanding the concepts and how they work together is one thing, but I hadn't actually built an app that used all these components together before.

As part of my initial research, I looked for existing tutorials or examples that did what I was trying to do. I found several good [blog](https://codeforgeek.com/manage-session-using-node-js-express-4/) [posts](https://medium.com/mtholla/managing-node-js-express-sessions-with-redis-94cd099d6f2f) [and](https://blog.risingstack.com/node-hero-node-js-authentication-passport-js/) [tutorials](https://medium.com/@evangow/server-authentication-basics-express-sessions-passport-and-curl-359b7456003d), but none did exactly what I was looking for. Part 1 of this tutorial will take you step-by-step through the process of building a web app with Node.js and Express that uses [express-session](https://github.com/expressjs/session) and [connect-redis](https://github.com/tj/connect-redis) as a way of helping users understand how session management works. Part 2 will expand on this by implementing authentication using [Passport](http://www.passportjs.org/) and exploring how authentication and sessions work together.

### Get the code for the craft beer name demo app

We will be starting with a simple demo app, and once we have that up and running, we'll add in session management and then authentication. Let's start by cloning the GitHub repo that has the code for the demo app and then switch to the beer-demo branch.

```
$ git clone https://github.com/jankleinert/redis-session-demo
$ cd redis-session-demo
$ git checkout beer-demo 
```						

Let's try running the app to make sure it works.

```
$ npm install
$ npm run dev
``` 
						

Open [http://localhost:3000](http://localhost:3000) in your browser, and you should see something like this.

![demo app screenshot](https://jankleinert.com/assets/images/blog/node-redis-beer-demo.png)

### Understanding the demo app

The demo app was built using [express-generator](https://expressjs.com/en/starter/generator.html) to create the app skeleton. It's using [Pug](https://pugjs.org/api/getting-started.html) for the view engine. When you click the Pour Another button, it makes a request to an [API](https://www.craftbeernamegenerator.com/about.html) that will return a machine-learning-generated craft beer name. That's really all the app does at this point.

The three main files we'll be working with are `app.js`, `/routes/index.js`, and `/views/index.pug`.

![dir structure](https://jankleinert.com/assets/images/blog/node-redis-dir-structure.png)

### Why do we care about session management anyway?

"Session" is such an overloaded term, and can mean very different things depending on context. In this tutorial, we're talking about a user’s session in a web application. You can think of it as the set of requests and responses within a web app, initiated by a single user, from the start of their interaction until they end the session or it expires.

Why do we care about or need a construct like a session? HTTP is stateless, so each request and response pair is independent of the others. By default, no state is maintained and the server doesn’t know who you are from one request to another. Session management gives us the ability to assign an identifier to a user session, and use that ID to store state or data relevant to the session. This could be something like whether or not a user is authenticated, the items in a shopping cart, and so on - whatever state needs to be kept during that session.

There are multiple ways of handling session management, but we’re going to look at one specific way, where session data is kept in a session store, and we’ll be using Redis as the session store.

![client and server](https://jankleinert.com/assets/images/blog/node-redis-cookie.png)

On the client side, a cookie is stored with the session ID but none of the session data. In your application’s session store (Redis in this case), the session ID is also stored, along with the session data.

### Add a session info panel to the app

To make it easy to visualize what's happening with a session, we will add a session info panel to the app. Open `/views/index.pug` and add the following code to the bottom of the file. Be careful with your indentation; `.session` should line up in the same column as `h1`.

```    
    .session
      p Session Info
      if sessionID
        p= 'Session ID: ' + sessionID 
      if sessionExpireTime
        p= 'Session expires in ' + Math.round(sessionExpireTime) + ' seconds'
      if beersViewed
        p= 'Beers viewed in this session: ' + beersViewed							
```  						

This panel will display the session ID, how many more seconds are left before the session expires, and also our session data: the number of beer names that have been viewed in this session. We'll be specifying those values in `/routes/index.js` in a later step.

### Add express-session and connect-redis to app.js

`express-session` is session middleware for Express. It's pretty straightforward to set up and use. There are quite a few [compatible session stores](https://github.com/expressjs/session#compatible-session-stores) that you can use for storing session data. We will be using [`connect-redis`](https://www.npmjs.com/package/connect-redis). Let's start by installing the npm modules that we need.

```    
$ npm install --save express-session uuid redis connect-redis						
```    						

Next, open up `app.js` and add the following code below the existing `require`s. `uuid` will be used to generate a unique ID to use for our session ID.

```    
const uuid = require('uuid/v4')
const session = require('express-session');
const redis = require('redis');
const redisStore = require('connect-redis')(session);	
    
const redisClient = redis.createClient();
    
redisClient.on('error', (err) => {
  console.log('Redis error: ', err);
});
```    						

Before we move on, make sure you have Redis installed and that the Redis server is running. If you need to install Redis, you can take a look at [this documentation](https://redis.io/topics/quickstart). Now we can set up the session middleware and tell it to use our Redis store as the session store. Add this code above the line `app.use('/', indexRouter);`.

```    
app.use(session({
  genid: (req) => {
    return uuid()
  },
  store: new redisStore({ host: 'localhost', port: 6379, client: redisClient }),
  name: '_redisDemo', 
  secret: process.env.SESSION_SECRET,
  resave: false,
  cookie: { secure: false, maxAge: 60000 }, // Set to secure:false and expire in 1 minute for demo purposes
  saveUninitialized: true
}));							
```    						

There are a couple things to note about this code. The cookie that stores the session ID will be named "\_redisDemo". We are using an environment variable to set the secret. In the next step, we'll export that env variable (you can set it to whatever you like). We are setting the session expiration to 1 minute to make it easier to understand what's happening in the demo app. In a real application, you'd set the maxAge to something more reasonable for your application. In your terminal, stop `nodemon` and then run the following.

```    
$ export SESSION_SECRET=some_secret_value_here && npm run dev					
```    						

### Add session management code to /routes/index.js

The last step will be to add logic to keep track of the number of beer names viewed per session and to pass the session-related information through to the session panel. Open `/routes/index.js` and replace the existing `get` and `post` with the code below.

```    
router.get('/', function(req, res, next) {
  var expireTime = req.session.cookie.maxAge / 1000; 
  res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: null });
});
    
router.post('/', function (req, res) {
  request('https://www.craftbeernamegenerator.com/api/api.php?type=trained', function (err, response, body) {
    if (req.session.views) {
      req.session.views++
    } else {
      req.session.views = 1
    }
    var expireTime = req.session.cookie.maxAge / 1000;   
    
    if(err){
      res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
    } else {
      var beerInfo = JSON.parse(body)
    
      if(beerInfo.status != 200){
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
      } else {
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: beerInfo.data.name, beerStyle: beerInfo.data.style, error: null});
      }
    }
  });
});							
```    						

What did we change? In `router.get`, we added `expireTime` so that we can calculate the amount of time until the session expires. Then in `res.render`, we are passing some additional values: the session ID from `req.sessionID`, the expire time we just calculated, and the number of beers viewed per session, which is stored as `req.session.views`. On the first page view of a session, there will not be a value for `req.session.views`, but our template knows how to handle that.

In `router.post`, after we make the API request for the beer name, we are either incrementing `req.session.views` or setting it to `1` if this is the first beer name viewed in the session. Then, similar to what we saw above, we're passing along the additional session-related information in `res.render`.

### Session management in action!

With everything in place now, open [http://localhost:3000](http://localhost:3000) in your browser. When it first loads, you should see the info panel displays a session ID and a time until the session expires.

Click on the Pour Another button (within 60 seconds, so your session doesn't expire), and you should see that the session ID remains the same, and now you also see the number of beers viewed in the session set to `1`. If you open dev tools in your browser and view cookies, you should see a cookie named `_redisDemo`, and part of its value will contain the session ID.

![session with cookie](https://jankleinert.com/assets/images/blog/node-redis-session-id-cookie.png)

Finally, if you start `redis-cli` and then issue the following command, where `YOUR_SESSION_ID` is replaced with the session ID shown in your browser, you should see the session data that's being stored in Redis for that session, including the views.

```    
$ redis-cli
$ get "sess:YOUR_SESSION_ID"							
```    						

The output should look something like this:

![redis-cli](https://jankleinert.com/assets/images/blog/node-redis-redis-cli.png)

Play around with the app some more to get a better understanding for how the sessions work. What happens if you close and then quickly re-open your browser? What happens if you wait more than 60 seconds and then refresh the page?

At this point, hopefully you have a better understanding of what session management is and how to implement it for a Node.js app using `express-session` and `connect-redis`. In Part 2, we'll build on what we've done in this tutorial by adding authentication to the app using Passport.


Just want the code from Part 1? Get it here:
{% github jankleinert/redis-session-demo %}