Create a LocalStorage polyfill for non-browser environments
Tags: javascript, webdev, tutorial, learning
Published: 2024-02-02 12:47:41

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`.

```js
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;
    }
}
```

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.

```js
const polyFill = new LocalStoragePolyFill();
```

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.

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

polyFill.setItem('age', 42);
polyFill.getItem('age');    // 42
```

## 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:

```js
console.log(polyFill.name);     // `John Smith`
console.log(polyFill['name']);  // `John Smith`
```

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

```js
polyFill.age = 30;
console.log(polyFill['age']);   // 30
```

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:

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

const localStoragePolyFill = new Proxy(polyFill, handler);
```

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.

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

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.

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

localStoragePolyFill['age'] = 25;
console.log(localStoragePolyFill.age);      // 25
```

## 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](https://github.com/phuocng/javascript-proxy) 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:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)