hapi — How to Manage Cookies and HTTP States Across Requests

HTTP as a stateless protocol won’t keep the client’s state across different requests in your application. A common example for this scenario is the log-in and afterwards remember the user credentials for a defined time interval, like until the browser window gets closed. In case you don’t save the HTTP state across requests, the user needs to sign in again on every page that requires authentication.

This tutorial will show you how to save states for a client across requests and also how to update the associated data or remove the state at all.

hapi Series Overview

Prepare a Cookie

HTTP state management in hapi is done using cookies. Cookie management is a two-step process in hapi. At first, you need to tell your server what state should be stored by providing a name and an object of options. To prepare your server for a cookie, use the server.state(name, options) method that tells the server to create a cookie with name and the related options.

The following example illustrates a very basic cookie configuration where you’re going to save a cookie for one day and encode its data as JSON and afterwards Base64.

hapi v17

const Hapi = require('hapi')

// create new server instance
const server = new Hapi.Server({  
  host: 'localhost',
  port: 3000
})

server.state('session', {  
  ttl: 1000 * 60 * 60 * 24,    // 1 day lifetime
  encoding: 'base64json'       // cookie data is JSON-stringified and Base64 encoded
})

hapi v16

const Hapi = require('hapi')

// create new server instance
const server = new Hapi.Server()

// add server’s connection information
server.connection({  
  host: 'localhost',
  port: 3000
})

server.state('session', {  
  ttl: 1000 * 60 * 60 * 24,    // 1 day lifetime
  encoding: 'base64json'       // cookie data is JSON-stringified and Base64 encoded
})

Notice that you use the state() method on your server object. If you split your application into multiple plugins, you’ve access to your server instance within the plugin itself. Depending on where you want to set your cookie definitions, you can do that individually in your plugins or globally outside the plugins.

There are multiple cookie options that you can use and benefit from. Please refer to the server state options in hapi’s API reference to read the complete list of available settings.

Create a Cookie

Your server is prepared to save a cookie called session and the cookie has a time to live of one day. To save a state and data to your cookie, you leverage the reply interface and call the state() method. Please notice, that the method name is the same for your server preparation and the cookie store functionality. In the end, both methods perform different tasks.

The following code snippet will get ahead on how to read a cookie to get its data. However, the important part is how to set the cookie data. As you can see in the example below, you read the existing cookie value, check if it’s already defined, update the lastVisit property and finally reply the Hello Future Studio message with a saved the state within the session cookie.

hapi v17

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: (request, h) => {
      let cookie = request.state.session

      if (!cookie) {
        cookie = {
          username: 'futurestudio',
          firstVisit: false
        }
      }

      cookie.lastVisit = Date.now()

      return h.response('Hello Future Studio').state('session', cookie)
    }
  }
})

hapi v16

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: function (request, reply) {
      var cookie = request.state.session

      if (!cookie) {
        cookie = {
          username: 'futurestudio',
          firstVisit: false
        }
      }

      cookie.lastVisit = Date.now()

      reply('Hello Future Studio').state('session', cookie)
    }
  }
})

Your session cookie now includes the username, indicates that the next request is not your first one and the time of your last visit. The lastVisit property is a custom field and is not used to evaluate the lifetime of the cookie itself.

Create Multiple Cookies

Up to this point, you’ve created a single cookie. There are situations where you want to create another one or multiple at the same time. The functionality is already available and you can just go ahead and chain the .state(name, data) method on your reply interface.

hapi v17

// prepare another cookie without encoding
// value must be string if no encoding is set
server.state('email', {  
  ttl: 1000 * 60 * 60 * 24 * 7    // 7 days lifetime
})

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: (request, reply) => {
      let email = request.state.email
      let session = request.state.session

      if (!email) {
        email = 'info@futurestud.io'
      }


      if (!session) {
        session = { username: 'futurestudio', firstvisit: false }
      }

      cookie.lastVisit = Date.now()

      return h.response('Hello Future Studio')
        .state('session', session)
        .state('email', email)
    }
  }
})

hapi v16

// prepare another cookie without encoding
// value must be string if no encoding is set
server.state('email', {  
  ttl: 1000 * 60 * 60 * 24 * 7    // 7 days lifetime
})

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: function (request, reply) {
      var email = request.state.email
      if (! email) {
        email = 'info@futurestud.io'
      }

      var session = request.state.session
      if (! session) {
        session = {
          username: 'futurestudio',
          firstvisit: false
        }
      }

      cookie.lastVisit = Date.now()

      reply('Hello Future Studio')
        .state('session', session)
        .state('email', email)
    }
  }
})

The first part of this snippet prepares the cookie via the server.state(name, options) method. Afterwards, you get the current cookie values for session and email, update individual fields if necessary on both objects and ultimately reply the request including both cookies.

Read a Cookie

Within the request.state object, you’ll find all your cookie data and you can access them using their defined name. Remember the server preparation from above? There you set your cookie name which is now used to access the data.

hapi v17

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: (request, h) {
      const cookie = request.state.session

      if (cookie) {
        // use cookie values
      }

      return 'Hello Future Studio'
    }
  }
})

hapi v16

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: function (request, reply) {
      var cookie = request.state.session

      if (cookie) {
        // use cookie values
      }

      reply('Hello Future Studio')
    }
  }
})

If you already stored data for your session cookie, you can use the data to perform further processing.

Update a Cookie Value

To update the cookie data, just go ahead and apply the same method as you would do to create it: reply().state(name, data). Because you never know if the cookie data is still existing due to cookie lifetime restrictions, make sure to check whether your cookie data is accessible after requesting it with request.state.cookieName.

hapi v17

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: (request, h) {
      let updatedCookie = request.state.session

      if (updatedCookie) {
        updatedCookie.lastVisit = Date.now()
      }

      return h.response('Hello Future Studio').state('session', updatedCookie)
    }
  }
})

hapi v16

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: function (request, reply) {
      var updatedCookie = request.state.session

      if (updatedCookie) {
        updatedCookie.lastVisit = Date.now()
      }

      reply('Hello Future Studio').state('session', updatedCookie)
    }
  }
})

The reply().state(name, data) method will override your existing data immediately.

Delete a Cookie

If you manually want to delete the cookie’s data, use the reply().unstate(name) method.

hapi v17

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: (request, reply) => {
      return h.response('Hello Future Studio').unstate('session')
    }
  }
})

hapi v16

server.route({  
  method: 'GET',
  path: '/',
  config: {
    handler: function (request, reply) {
      reply('Hello Future Studio').unstate('session')
    }
  }
})

This will reset your cookie data and you don’t need to wait for the cookie ttl to exceed until the actual data gets deleted.

Outlook

The built-in support for HTTP state management in hapi lets you easily store user-related data and access it across multiple requests. Various cookie options allow you to specify cookie lifetime, encoding, and many more.

Do you have any question or just want to leave a message? Use the comments below or find us on Twitter @futurestud_io

Make it rock & enjoy coding!


Additional Resources

Explore the Library

Find interesting tutorials and solutions for your problems.