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, andclear()
, 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;
}
}
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();
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
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`
You can update the value of a key by using a similar syntax as well.
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:
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.
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.
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 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)