Ten Client-side Storage Options

Storing and manipulating data in the browser — also known as client-side storage — is useful when it’s not necessary or practical to send it to the web server.

Situations for storing and manipulating data in the browser include:

  • retaining the state of a client-side application — such as the current screen, entered data, user preferences, etc.
  • utilities which access local data or files and have strict privacy requirements
  • progressive web apps (PWAs) which work offline

Here are ten ways to store browser data:

  • with JavaScript variables
  • via DOM Node Storage
  • via Web Storage (localStorage and sessionStorage)
  • with IndexedDB
  • with the Cache API (don’t use AppCache!)
  • via the File System Access API
  • via the File and Directory Entries API
  • with cookies
  • by using window.name
  • with WebSQL

This article digs into these ten different ways to store data in the browser, covering their limits, pros, cons, and the best uses of each technique.

Before we browse the options, a quick note about data persistence …

Data Persistence

In general, data you store will either be:

  1. persistent: it remains until your code chooses to delete it, or
  2. volatile: it remains until the browser session ends, typically when the user closes the tab

The reality is more nuanced.

Persistent data can be blocked or deleted by the user, operating system, browser, or plugins at any point. The browser can decide to delete older or larger items as it approaches the capacity allocated to that storage type.

Browsers also record page state. You can navigate away from a site and click back or close and re-open a tab; the page should look identical. Variables and data regarded as session-only are still available.

1. JavaScript Variables

metric comment
capacity no strict limit but browser slowdowns or crashes could occur as you fill memory
read/write speed the fastest option
persistence poor: data is wiped by a browser refresh

Storing state in JavaScript variables is the quickest and easiest option. I’m sure you don’t need an example, but …

const
  a = 1,
  b = 'two',
  state = 
    msg:  'Hello',
    name: 'Craig'
  ;

Advantages:

  • easy to use
  • fast
  • no need for serialization or de-serialization

Disadvantages:

  • fragile: refreshing or closing the tab wipes everything
  • third-party scripts can examine or overwrite global (window) values

You’re already using variables. You could consider permanently storing variable state when the page unloads.

2. DOM Node Storage

metric comment
capacity no strict limit but not ideal for lots of data
read/write speed fast
persistence poor: data can be wiped by other scripts or a refresh

Most DOM elements, either on the page or in-memory, can store values in named attributes. It’s safer to use attribute names prefixed with data-:

  1. the attribute will never have associated HTML functionality
  2. you can access values via a dataset property rather than the longer .setAttribute() and .getAttribute() methods.

Values are stored as strings so serialization and de-serialization may be required. For example:

// locate <main> element
const main = document.querySelector('main');

// store values
main.dataset.value1 = 1;
main.dataset.state = JSON.stringify( a:1, b:2 );

// retreive values
console.log( main.dataset.value1 ); // "1"
console.log( JSON.parse(main.dataset.state).a ); // 1

Advantages:

  • you can define values in JavaScript or HTML — such as <main data-value1="1">
  • useful for storing the state of a specific component
  • the DOM is fast! (contrary to popular opinion)

Disadvantages:

  • fragile: refreshing or closing the tab wipes everything (unless a value is server-rendered into the HTML)
  • strings only: requires serialization and de-serialization
  • a larger DOM affects performance
  • third-party scripts can examine or overwrite values

DOM node storage is slower than variables. Use it sparingly in situations where it’s practical to store a component’s state in HTML.

3. Web Storage (localStorage and sessionStorage)

metric comment
capacity 5MB per domain
read/write speed synchronous operation: can be slow
persistence data remains until cleared

Web Storage provides two similar APIs to define name/value pairs. Use:

  1. window.localStorage to store persistent data, and
  2. window.sessionStorage to retain session-only data while the browser tab remains open (but see Data Persistence)

Store or update named items with .setItem():

localStorage.setItem('value1', 123);
localStorage.setItem('value2', 'abc');
localStorage.setItem('state', JSON.stringify( a:1, b:2, c:3 ));

Retrieve them with .getItem():

const state = JSON.parse( localStorage.getItem('state') );

And delete them with .removeItem():

localStorage.removeItem('state')

Other properties and methods include:

  • .length: the number of items stored
  • .key(N): the name of the Nth key
  • .clear(): delete all stored items

Changing any value raises a storage event in other browser tabs/windows connected to the same domain. Your application can respond accordingly:

window.addEventListener('storage', s => 

  console.log(`item changed: $ s.key `);
  console.log(`from value  : $ s.oldValue `);
  console.log(`to new value: $ s.newValue `);

);

Advantages:

  • simple name/value pair API
  • session and persistent storage options
  • good browser support

Disadvantages:

  • strings only: requires serialization and de-serialization
  • unstructured data with no transactions, indexing, or search
  • synchronous access will affect the performance of large datasets

Web Storage is ideal for simpler, smaller, ad-hoc values. It’s less practical for storing large volumes of structured information, but you may be able to avoid performance issues by writing data when the page unloads.

4. IndexedDB

metric comment
capacity depends on device. At least 1GB, but can be up to 60% of remaining disk space
read/write speed fast
persistence data remains until cleared

IndexedDB offers a NoSQL-like low-level API for storing large volumes of data. The store can be indexed, updated using transactions, and searched using asynchronous methods.

The IndexedDB API is complex and requires some event juggling. The following function opens a database connection when passed a name, version number, and optional upgrade function (called when the version number changes):

// connect
function dbConnect(dbName, version, upgrade) 

  return new Promise((resolve, reject) => 

    const request = indexedDB.open(dbName, version);

    request.onsuccess = e => 
      resolve(e.target.result);
    ;

    request.onerror = e => 
      console.error(`indexedDB error: $ e.target.errorCode `);
    ;

    request.onupgradeneeded = upgrade;

  );

The following code connects to a myDB database and initializes a todo object store (analogous to a SQL table or MongoDB collection). It then defines an auto-incrementing key named id:

(async () => 

  const db = await dbConnect('myDB', 1.0, e => 

    db = e.target.result;
    const store = db.createObjectStore('todo',  keyPath: 'id', autoIncrement: true );

  )

)();

Once the db connection is ready, you can .add new data items in a transaction:

db.transaction(['todo'], 'readwrite')
  .objectStore('todo')
  .add( task: 'do something' )
  .onsuccess = () => console.log( 'added' );

And you can retrieve values, such as the first item:

db.transaction(['todo'], 'readonly')
  .objectStore('todo')
  .get(1)
  .onsuccess = data => console.log( data.target.result );
  //  id: 1, task: 'do something' 

Advantages:

  • flexible data store with the largest space
  • robust transactions, indexing, and search options
  • good browser support

Disadvantages:

  • a complex callback and event-based API

IndexedDB is the best option for reliably storing large quantities of data, but you’ll want to reach for a wrapper library such as idb, Dexie.js, or JsStore.

Continue reading
10 Client-side Storage Options and When to Use Them
on SitePoint.