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 define 2 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 will write a 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

Here’s an example of how you could use LocalStorage with the 2 wrapper functions we defined above –

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 the wrapper functions get()/set() 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.js package found on NPM.


LocalStorage-slim

LocalStorage-slim.js is a lightweight (< 1 kb) 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/obfuscate your data too.

A small example using localstorage-slim to setup ttl is shown below –

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.config.ttl = 5;                           // sets ttl globally (for all items)

When the ttl expires, the associated key-value is removed from the localStorage. Also note that ttl set using the ls.set(...) API overrides the global ttl giving you greater control over these features.


Encryption with LocalStorage-slim

As mentioned earlier, LocalStorage-slim.js allows you to encrypt the data stored in localStorage too. The default implementation only obfuscates data, which should suffice most of the times. You can also write your own encryption algorithm or use existing encryption algorithms like AES, 3DES, etc. via CryptoJS if that’s something you’d like. Here’s a complete tutorial with several examples showcasing encryption/decryption via CryptoJS in action.

A basic example of using encryption(obfuscation in this case) is shown below –

import ls from 'localstorage-slim';

ls.config.encrypt = true;            // set up encryption/obfuscation globally
ls.set('keyName', 'keyValue');
localStorage.getItem('keyName');     // "ÀÇÏx¹Ê½Ñ" (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

Leave a Reply