LocalStorage with TTL (Time to expiry)

If you are familiar with the LocalStorage API, you know that there is no provision for setting a TTL. This post aims to show you an easy way to write up a simple wrapper to have LocalStorage with TTL.

Localstorage with TTL
To setup TTL with LocalStorage, all we need to do is –

  • Store items with TTL (i.e. set to expire within a given time frame)
  • Retrieve items that haven’t expired

To achieve our objective, we will write 2 simple wrappers as outlined below.


1. Storing data with TTL

To store data with TTL in localStorage, lets write a simple wrapper function set():

/**
 * @param {string} keyName - A key to identify the value.
 * @param {any} keyValue - A value associated with the key.
 * @param {number} ttl- Time to live in seconds.
 */
const set = (keyName, keyValue, ttl) => {
    const data = {
        value: keyValue,                  // store the value within this object
        ttl: Date.now() + (ttl * 1000),   // store the TTL (time to live)
    }

    // store data in LocalStorage 
    localStorage.setItem(keyName, JSON.stringify(data));
};

As you can see, the wrapper function is very similar to LocalStorage’s setItem() method taking in one additional parameter ttl. The TTL is set to a time in the future. The next obvious step is to only return values for which the TTL has not expired.

2. Retrieving data with TTL

Our goal is to return only the data which has not expired. So to do that, we can write a simple wrapper function get():

/**
 * @param {string} keyName - A key to identify the data.
 * @returns {any|null} returns the value associated with the key if its exists and is not expired. Returns `null` otherwise
 */
const get = (keyName) => {
    const data = localStorage.getItem(keyName);
    if (!data) {     // if no value exists associated with the key, return null
        return null;
    }

    const item = JSON.parse(data);

    // If TTL has expired, remove the item from localStorage and return null
    if (Date.now() > item.ttl) {
        localStorage.removeItem(key);
        return null;
    }

    // return data if not expired
    return item.value;
};

As seen, the get() methods signature is exactly the same as that of LocalStorage’s getItem() method. Nothing special going on there.

Example of LocalStorage with TTL

A simple example of using LocalStorage with the 2 wrapper functions we just defined is shown below:

set(
  'car',                                    // keyName
  { name: 'Tesla Model 3', year: 2021 },    // keyValue
  60                                        // ttl in seconds
);

console.log('Car Value =>', localStorage.getItem('car')); 
// "{"value":{"name":"Tesla Model 3","year":2021},"ttl":1616000345920}"

/* 
* Call get() after 5 seconds.
*/
let res = get('car');    // { name: 'Tesla Model 3',  year: 2021 }
/*
* Call get() after 1 minute
*/
res = get('car');        // null

Going by the above example, calling get('car'); within 60 seconds returns the value since it hasn’t expired by then. But anytime after 60 seconds, you get null indicating that the value has expired. The item also gets removed from the localStorage.


Caveats of LocalStorage

Even though the functions we defined earlier work as a proof of concept, there are some things that you must note and account for. These are not limited to the wrapper functions we defined but to LocalStorage itself as well:

  • Every item stored with the above functions needs to be supplied with a TTL and will not work if not supplied with a TTL
  • There is no automatic way of flushing/removing expired items
  • There is no checking for the support of LocalStorage for your browser (for example, in incongito mode, LocalStorage access is not permitted)
  • There is no checking if storage limits have been reached (Many Browsers allot ~5mb of LocalStorage per domain)
  • JSON serialization of cyclic data could fail and there is no protection against it
  • Data stored in LocalStorage is stored in plain text and readable by anyone

If you decide to use LocalStorage and the get()/set() functions in production, ensure to have also accounted for the aforementioned caveats of LocalStorage or if you don’t wish to reinvent the wheel, you could also use the localstorage-slim package found on NPM.


LocalStorage-slim

LocalStorage-slim.js is a lightweight LocalStorage wrapper that takes care of all the aforementioned caveats. What is advantageous is that using ttl is completely optional. Another interesting optional feature is that it allows you to encrypt your data via CryptoJS if that’s something you’d like.

Here’s a simple example using localstorage-slim:

import { ls } from 'localstorage-slim';

// set a ttl
ls.set('keyName', 'keyValue', { ttl: 10 });  // 3rd argument is optional
ls.get('keyName');                           // keyValue (after 10 seconds, returns null)
ls.ttl = 5;                                  // sets ttl globally (for all items)

// set up encryption/obfuscation globally
ls.encrypt = true;
ls.set('keyName', 'keyValue');
localStorage.getItem('keyName');     // "kfdoiadf" (obfuscated string in our case)
ls.get('keyName');                   // "keyValue" (decrypts and returns correct value)

There are other libraries that also allow setting up TTL as well. Feel free to check them out in the references section found below. Hope that’s helpful 🙂


References