DEV Community

Cover image for Create a LocalStorage polyfill for non-browser environments
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Create a LocalStorage polyfill for non-browser environments

Local storage is a web technology that allows web applications to store data in the user's browser. This feature enables developers to create more engaging applications that can work offline or with limited network connectivity.

To use local storage, developers store key-value pairs as strings in the browser's memory. This data persists even when the user closes the browser or shuts down their computer, making it ideal for storing small amounts of data like user preferences, session information, or application settings.

While local storage has become increasingly popular among web developers due to its simplicity and versatility, it does have one major drawback: it's not available natively in non-browser environments like NodeJS. This presents a challenge for developers who want to use this feature in their server-side applications.

To overcome this challenge, we'll walk you through the process of building a LocalStorage polyfill for NodeJS. This polyfill stores data in memory rather than the browser's storage, but it still provides the same functionality as LocalStorage.

Understanding the LocalStorage API

Before we dive into building a polyfill for LocalStorage in NodeJS, let's first introduce the common API that is used across all environments.

  • setItem() stores a key-value pair in the browser's memory. If a key already exists, its value will be updated.
  • getItem() retrieves the value associated with a key from storage. If the key does not exist, it will return null.

It's important to note that all values stored in LocalStorage are converted to strings before being saved. Therefore, when retrieving values with getItem(), you may need to parse them back into their original data type (such as using JSON.parse() for objects).

  • In addition to these two methods, there are also removeItem(key), which removes a specific key-value pair from storage, and clear(), which removes all data from storage.
  • The key(i) method returns the name of the key at the specified index in storage. This is useful when you need to access keys dynamically or iterate over all keys stored in LocalStorage.
  • The LocalStorage API also provides a property called length. This property returns an integer value that represents the number of key-value pairs currently stored in LocalStorage.

The length property is particularly useful when iterating over all items stored in LocalStorage. You can use it to determine how many times you need to loop through and retrieve each item using getItem().

Now that we have an understanding of the LocalStorage API, let's move on to building our polyfill for NodeJS.

Creating a Polyfill for the LocalStorage API

In the previous section, we introduced some common LocalStorage APIs. Here, we'll create a single class that mimics those APIs. The LocalStoragePolyFill class we define below behaves like the LocalStorage API methods you're familiar with. We use a Map() object to store key-value pairs in memory, and we call it values.

const values = new Map();

class LocalStoragePolyFill {
    getItem(key) {
        return (values.has(key)) ? String(values.get(String(key))) : null;
    }

    setItem(key, val) {
        values.set(String(key), String(val));
    }

    clear() {
        values.clear();
    }

    removeItem(key) {
        values.delete(key);
    }

    key(i) {
        const arr = Array.from(values.keys());
        return arr[i];
    }

    get length() {
        return values.size;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's go through the methods provided by the LocalStoragePolyFill class.

First up is the getItem(key) method. When called, it checks if the provided key exists in the map and returns its corresponding value as a string. If the key does not exist, it returns null.

The setItem(key, val) method takes a key-value pair as arguments and saves them in the map. If a key already exists, its value will be updated with the new value provided.

Next, the removeItem(key) method removes a specific key-value pair from storage by deleting it from the map.

The clear() method removes all data from storage by calling on the clear() method of our Map object.

The key(i) method takes an index as its argument and returns the name of the key at that index in storage. This is useful when you need to access keys dynamically or iterate over all keys stored in LocalStorage.

Lastly, we define a getter for the property length. When called, it returns an integer value that represents the number of key-value pairs stored in our Map object.

The LocalStoragePolyFill class may not have any fancy features, but it gets the job done by using a Map and its built-in operators to store the key-value pair data structure.

To give the polyfill a try, we create a new instance of the LocalStoragePolyFill class.

const polyFill = new LocalStoragePolyFill();
Enter fullscreen mode Exit fullscreen mode

Next, we update the value of a given key using the setItem function. To retrieve the corresponding value of the key, we use the getItem() function.

polyFill.setItem('name', 'John Smith');
polyFill.getItem('name');   // `John Smith`

polyFill.setItem('age', 42);
polyFill.getItem('age');    // 42
Enter fullscreen mode Exit fullscreen mode

Accessing key values with dot or bracket notation

In the previous section, we created a polyfill that worked great with supported APIs. However, the actual LocalStorage also allows you to access key values using dot or bracket notation.

For instance, you can directly access the value of the name key like this:

console.log(polyFill.name);     // `John Smith`
console.log(polyFill['name']);  // `John Smith`
Enter fullscreen mode Exit fullscreen mode

You can update the value of a key by using a similar syntax as well.

polyFill.age = 30;
console.log(polyFill['age']);   // 30
Enter fullscreen mode Exit fullscreen mode

To make it possible to access key-value pairs in our LocalStorage polyfill using either dot notation or bracket notation, we can use a JavaScript Proxy.

A Proxy object lets us define custom behavior for property access (getting and setting). Specifically, we can define a get trap that intercepts all property access attempts on our LocalStoragePolyFill instance.

In the get trap handler, we use hasOwnProperty() to check if the accessed property is a method of the LocalStoragePolyFill class. If it is, we simply return that method.

It's important to check if the property is a method of the class so that we don't intercept method calls and instead let them be called on the original object. This ensures that all methods behave as expected and aren't affected by our custom behavior.

If the accessed property is not a method of the class, we assume it's a key name and try to retrieve its corresponding value using the getItem() method.

Here's an example of what the get trap handler looks like:

const handler = {
    get: function(target, property) {
        return LocalStoragePolyFill.prototype.hasOwnProperty(property)
            ? target[property]
            : target.getItem(property);
    },
};

const localStoragePolyFill = new Proxy(polyFill, handler);
Enter fullscreen mode Exit fullscreen mode

To intercept all property assignments, we can define a set trap for our Proxy object. If the assigned property matches an existing method of the LocalStoragePolyFill class, then we call that method with the provided arguments. On the other hand, if the property is a key name, we try to set its value using the setItem() method.

const handler = {
    set: function(target, property, value) {
        LocalStoragePolyFill.prototype.hasOwnProperty(property)
            ? target[property] = value
            : target.setItem(property, value);
        return true;
    },
});
Enter fullscreen mode Exit fullscreen mode

Now, with this implementation, we can access and modify key-value pairs in our LocalStorage polyfill using either dot notation or bracket notation, just like we would with real LocalStorage.

localStoragePolyFill.name = 'Jane Doe';
console.log(localStoragePolyFill.name);     // 'Jane Doe'

localStoragePolyFill['age'] = 25;
console.log(localStoragePolyFill.age);      // 25
Enter fullscreen mode Exit fullscreen mode

Conclusion

In conclusion, we have successfully created a LocalStorage polyfill for NodeJS that mimics the behavior of the native LocalStorage API. By using JavaScript Proxy, we were able to add support for accessing key-value pairs with dot or bracket notation. This polyfill is useful for developers who want to use LocalStorage in their server-side applications but can't because it's not available natively in NodeJS environments.

It's important to note that while our polyfill provides similar functionality to the real LocalStorage, it does have some limitations. For example, it doesn't persist data across different sessions or between different instances of the application running on different machines. Additionally, it stores all data in memory which can cause performance issues when storing large amounts of data.

Overall, building a polyfill for LocalStorage in NodeJS is an excellent exercise that can help developers understand how browser APIs work and how they can be emulated in other environments.


If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

Top comments (0)