Powerful and versatile

Exploring the Power of Browser Storage

Exploring the Power of Browser Storage

Powerful and versatile

Exploring the Power of Browser Storage


When you think of storing data related to a website or web application, your first thought might be to build an API to save data to a relational database like MariaDB or a document database like MongoDB on a server. However, the browser has plenty of storage implementations that can be utilised for different purposes. Understanding the different storage facilities inside modern browsers is crucial for web developers. By examining their pros and cons, you can make well-informed decisions about what to use in your next project, empowering you to create efficient and effective web applications.

1. Cookies

Website cookies, or HTTP cookies, are tiny text files stored in a user’s browser when they visit a website. These cookies can contain information that helps the website remember the user or their preferences and improve the browsing experience.

Cookies are commonly used for authentication and session management. When a user logs into a website, a cookie with their login information is created on the server and stored in the browser. This allows the website to recognise the users and keep them logged in as they navigate different pages.

In addition, cookies are often used for personalisation purposes. They can store user preferences, such as language settings or display preferences so that the website can provide a customised experience.

Setting cookies

Setting cookies with JavaScript is somewhat cumbersome because it’s written as a string containing all the cookie’s attributes.

Example 1.1

document.cookie = "Favorite=Chocolate; path=/; max-age=31536000;";

In example 1.1, a cookie is created and saved with the name Sandwich and the value Turkey separated by an equal sign. The string also contains the cookie’s path and expiration date.

By default, the path is the current path, the page’s location that creates the cookie. If that path is a website subfolder, like test.com/cookies, the cookie will only be available for pages that are descendants of that path. If you want the cookie to be available on the whole website, make sure you include the root path, as in the example.

The max-age attribute defines a cookie's end date. You can provide a date using the expires attribute in GMT format, but giving a max-age in seconds is easier. In example 1.1, the cookie will be deleted after one year of storage. Omitting this attribute will turn this cookie into a session cookie, which means it will be deleted as soon as the browser tab or browser is closed.

Cookies are accessible through the browser DevTools. In Safari and Firefox, they are available on the Storage tab, and in Chrome and Edge, they are present on the Application tab. Be aware that users can also access the cookies this way and alter them at will. The same goes for all the storage options mentioned further in this article.

Deleting cookies

If needed, the cookie can be removed by setting the max-age to 0, as shown in example 1.2. It is matched on the name and path; the cookie’s value is irrelevant in this case.

Example 1.2

document.cookie = "Favorite=; path=/; max-age=0;";

A shopping website can also use cookies to remember the user's shopping cart items. A benefit of using a cookie is that it is automatically sent to the server on each request, giving it direct access to the data. I prefer to store the contents of a shopping cart in a database on the server and only save a unique reference for that cart in either a cookie or local storage.

Cookie Store

Setting a cookie, as in example 1.1, is a synchronous action. This means that any subsequent JavaScript execution has to wait until it is finished. Interacting with a cookie is also impossible from within a Service Worker.

To solve both these problems and to overcome the tedious process of setting a cookie with a string, the Cookie Store is available in Chromium-based browsers, such as Chrome, Edge, and most common Android browsers.

Service Worker

A Service Worker is a JavaScript file that mediates between the browser and the server. It can intercept and alter each request and reply to it from the server. It runs in a separate thread so that it won’t slow down any script related to the website. It also doesn’t have access to the website’s Document Object Model (DOM) or any cookies set in the browser, except when created with the Cookie Store.

Setting cookies

Setting cookies with the Cookie Store is more straightforward, as shown in example 1.3.

Example 1.3

cookieStore.set("Favorite", "Chocolate");

This will create a session cookie named Favorite with the value Chocolate. Passing an object can set more options, like the expiration date (unfortunately, max-age is unavailable); see example 1.4.

Example 1.4

const year = 365 * 24 * 60 * 60 * 1000;

cookieStore.set({
  name: "Favorite",
  value: "Chocolate",
  expires: Date.now() + year,
});

Deleting cookies

Deleting cookies is easy; a separate method only requires the cookie name (example 1.5).

Example 1.5

cookieStore.delete("Favorite");

2. Web Storage

Web Storage is a mechanism for storing data as short—or long-term key-value pairs. Since the keys and values are always strings, objects and arrays must be converted, as shown in examples 2.1 and 2.2.

Example 2.1: converting objects

JSON.stringify / JSON.parse;

Example 2.2: converting arrays

array.toString / string.split(“,”)

Local Storage

The data stored in Local Storage is not bound to a session (tab or window) and will persist and be available on the next visit. However, data saved in a "private browsing" or "incognito" session will be deleted afterwards.

Web Storage provides straightforward methods to store, retrieve and delete data.

Example 2.3

localStorage.setItem(key, value);

localStorage.getItem(key);

localStorage.removeItem(key);

A practical example is used on the website cfp.watch, where favourites are stored in Local Storage. Next time the user visits the website (with the same browser), these favourites will be available again.

Session Storage

Session Storage works the same as Local Storage with one big exception: stored data will be cleared when a session (tab or window) is closed.

The available methods are similar.

sessionStorage.setItem(key, value);

sessionStorage.getItem(key);

sessionStorage.removeItem(key);

Session Storage can store temporary data or state for a web application when you’re not using a state management library or frameworks like Redux or Pinia.

3. WebSQL

WebSQL was an attempt to bring SQLite to the browser to provide a robust way of storing and querying data. However, not all browser vendors were convinced, and Mozilla didn’t even attempt to implement it in their Firefox browser.

Another reason it wasn’t well adopted was probably the horrible API for writing queries with callbacks, as shown in example 3.1.

Example 3.1

openDatabase("mydatabase", 1, "mydatabase", 5000000, function (db) {
  db.transaction(
    function (tx) {
      tx.executeSql(
        "create table rainstorms (mood text, severity int)",
        [],
        function () {
          tx.executeSql(
            "insert into rainstorms values (?, ?)",
            ["somber", 6],
            function () {
              tx.executeSql(
                "select * from rainstorms where mood = ?",
                ["somber"],
                function (tx, res) {
                  var row = res.rows.item(0);
                  console.log(
                    "rainstorm severity: " +
                      row.severity +
                      ",  my mood: " +
                      row.mood
                  );
                }
              );
            }
          );
        }
      );
    },
    function (err) {
      console.log("boo, transaction failed!: " + err);
    },
    function () {
      console.log("yay, transaction succeeded!");
    }
  );
});

Support for WebSQL is only available in older versions of the major browsers and some browsers on Android.

4. IndexedDB

IndexedDB has more or less replaced WebSQL but as a NoSQL database.

IndexedDB tables are referred to as object stores, and they support transactions and indexes. Like Web Storage, keys are stored as strings, but the value can be anything from strings to objects, arrays, or even binary data.

Example 4.1

let db;

const openDB = indexedDB.open("groceries", 1);

By calling the open method, a specific database (first parameter) will be opened or created if that database doesn’t exist yet. The second parameter of this method is the database’s version. When this version is higher than the current version (including non-existent), it will trigger the onupgradeneeded event, as shown in example 4.2.

Example 4.2

openDB.onupgradeneeded = function (event) {
  db = openDB.result;
  const oldVer = event.oldVersion;
  if (oldVer < 1) {
    db.createObjectStore("groceryList", { keyPath: "product" });
  }
};

In this event, actions depending on the version can be performed, like creating tables/object stores, indexes or inserting data.

IndexedDB will fire an onsuccess event when everything went right and an onerror vent in case of errors, including the specific error.

Example 4.3

openDB.onsuccess = function () {
  console.info("Database opened successfully");
  db = openDB.result;
  const store = db
    .transaction("groceryList", "readwrite")
    .objectStore("groceryList");
  store.put({ product: "Orange Juice", amount: 1 });
};

openDB.onerror = function () {
  console.error(openDB.error);
};

After successfully opening the database, you can start a transaction to store data, for instance. Transactions are especially useful when multiple actions are performed, and none are allowed to fail. A transaction will ensure that all actions are reverted in case of a failure.

idb

While the syntax for using IndexedDB is much better than for WebSQL, there is still room for improvement. Which is why Jake Archibald, formerly from Google, wrote a library called idb. It uses promises instead of events, enabling developers to use async/await. It also provides shortcuts for common transactions like getAll, put, and delete.

Example 4.4 shows a shorter and easier implementation of examples 4.1 through 4.3 with the idb library.

Example 4.4

import "./idb.js";

let db;

const openDatabase = async () => {
  db = await idb.openDB("groceries", 1, {
    upgrade(db, oldVersion, newVersion, transaction, event) {
      if (oldVersion < 1) {
        db.createObjectStore("groceryList", { keyPath: "product" });
      }
    },
  });
};

await db.put("groceryList", { product: "Orange Juice", amount: 1 });

Data Synchronization

IndexedDB is an excellent solution for ensuring that data is always available for users, even offline or with a lousy internet connection. On startup, data from the server can be synced to IndexedDB for faster response, and when there is an active internet connection, added or changed data can be sent back to the server.

Examples 4.5 through 4.7 show a rudimentary implementation of this using a Web...