Overview of web storage
Web storage is data stored locally in a user’s browser. There are two types of web storage:
- Local storage – data with no expiration date that will persist after the browser window is closed.
- Session storage – data that gets cleared after the browser window is closed.
This is useful for saving data such as user preferences (light or dark color theme on a website), remembering shopping cart items, or remembering a user is logged into a website.
Previously, cookies were the only option for remembering this type of local, temporary data. Local storage has a significantly higher storage limit (5MB vs 4KB) and doesn’t get sent with every HTTP request, so it can be a better option for client-side storage.
Here is an overview of localStorage
methods.
Method | Description |
---|---|
setItem() | Add key and value to local storage |
getItem() | Retrieve a value by the key |
removeItem() | Remove an item by key |
clear() | Clear all storage |
You can test out what’s in local storage by going to the JavaScript console and typing it in. Actually do this, don’t just read it.
localStorage
Output:
Storage {length: 0}
Adding some data to localStorage
is as easy as using the setItem()
method. I’ll use a generic key and value for the names, but they can be any strings.
localStorage.setItem('key', 'value')
Now if you test localStorage
in the console again, you’ll find your new key and value.
Output:
Storage {key: "value", length: 1}
If you want to get the value for a particular key, you’ll use the getItem()
method.
localStorage.getItem('key')
Output:
value
Finally, you can remove the data with removeItem()
.
localStorage.removeItem('key')
Using clear()
will clear all local storage.
localStorage.clear()
Now we can begin setting up the app.
Setting up the front end
First, we’ll create a simple HTML front end with index.html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Tab App</title>
<link rel="stylesheet" href="https://jslib.dev/sandbox/tab/css/main.css"/>
</head>
<body>
<div class="small-container">
<h1>New Tab App</h1>
<!-- more will go here -->
</div>
<script src="js/scripts.js"></script>
</body>
</html>
We’re going to set up with three things:
- A text input – for adding new items.
- A list – where the items will be added on the front end.
- A button – to clear all items.
Add this code where the “more will go here” comment is.
<form>
<input id="item" type="text" placeholder="New" required />
</form>
<h2>Items</h2>
<ul></ul>
<button>Clear All</button>
Here’s what it looks like:
And that’s all for the front end. From here, we’ll focus on adding functionality with JavaScript.
Setting up JavaScript functionality
Before we integrate this into local storage, let’s just get the form and list working – we want anything we submit in the input
to appear in the ul
.
First, I’m just going to set up some variables for the elements on the page – the form, the unordered list, the button, and the text input.
const form = document.querySelector('form')
const ul = document.querySelector('ul')
const button = document.querySelector('button')
const input = document.getElementById('item')
Next, I’m going to make a function that creates an li
element, since I’ll be doing that more than once. I’ll call the function liMaker()
. It just creates an li
element, sets the text of the element to the parameter, and appends the list item to the ul
.
const liMaker = (text) => {
const li = document.createElement('li')
li.textContent = text
ul.appendChild(li)
}
I’m going to add an event listener to the form that watches for a submit event – which will be any time you press enter on the form. The e.preventDefault()
will prevent the form from the default submit action, which we don’t want, since we’re not sending any data to a server.
Instead, the form will submit the value of the input
. We’re going to call the liMaker()
function, which will create the item with the text of the input
value and append it to the DOM. Finally, we’ll set the input
value to an empty string so you don’t have to erase the last item entered manually.
form.addEventListener('submit', function (e) {
e.preventDefault()
liMaker(input.value)
input.value = ''
})
Now with only a few lines of code, we have a little app that adds to-do items to a list.
Since we’re not saving the items anywhere, when you close or refresh the browser, the items will be gone. The final step is to integrate it into local storage so that the data persists.
Integrating local storage
Now we’re going to add a few more bits of functionality to the app. First, every time the form is submitted, the input
value should be added to the localStorage
as well as appear on the front end. We’ll also want to loop through all the existing local storage items and display them at the top of the list. Last, we want the “Clear All” button to remove all items from local storage as well as the front end.
Let’s create an empty array to start, and create a localStorage
key called “items”. Now, localStorage
only supports strings as values, and want to store our to-dos in an array.
We can get around this by using JSON.stringify()
to convert a data array to a string. We’ll use JSON.parse()
to convert the contents of localStorage
back into something we can work with later in the data
variable. Put this code below all the constant declarations we set earlier.
// other constant declarations here
let itemsArray = []
localStorage.setItem('items', JSON.stringify(itemsArray))
const data = JSON.parse(localStorage.getItem('items'))
In the form event listener, let’s push any new input
value into the array, then set the localStorage
to the new, updated value.
// form event listener here
e.preventDefault()
itemsArray.push(input.value)
localStorage.setItem('items', JSON.stringify(itemsArray))
We’re going to loop through everything in our data
variable above, which contains all the existing localStorage
data in a form JavaScript can understand and work with, and we’ll run the liMaker()
again. This will display all existing stored information on the front end every time we open the app.
data.forEach((item) => {
liMaker(item)
})
Last, we’ll add a click event to the button that will clear all data from localStorage
, as well as removing every child node from the ul
.
button.addEventListener('click', function () {
localStorage.clear()
while (ul.firstChild) {
ul.removeChild(ul.firstChild)
}
})
If all went well, everything will save to storage as well as appear on the front end, which you can check by testing localStorage
in the console.
Storage {items:
"["Welcome","to","the","Thunderdome"]",
length: 1}
There’s one final problem: after closing the browser or reloading the page, all the existing information in localStorage
is gone, and nothing remains on the front end. Why?
Our itemsArray
is being reset to an empty array every time the script runs. We could fix this by making a conditional statement that checks if localStorage
already exists, such as in the below example.
let items
if (localStorage.getItem('items')) {
items = JSON.parse(localStorage.getItem('items'))
} else {
items = []
}
A little more concise would be to use a ternary operator to do the same thing.
let itemsArray = localStorage.getItem('items')
? JSON.parse(localStorage.getItem('items'))
: []
With that, our app is complete! Now when you enter in some information and refresh or close the browser window, the data will persist until you manually clear the data in Developer Tools (under Application -> Storage) or by running the localStorage.clear()
command. Here is the full JavaScript code.
const form = document.querySelector('form')
const ul = document.querySelector('ul')
const button = document.querySelector('button')
const input = document.getElementById('item')
let itemsArray = localStorage.getItem('items')
? JSON.parse(localStorage.getItem('items'))
: []
localStorage.setItem('items', JSON.stringify(itemsArray))
const data = JSON.parse(localStorage.getItem('items'))
const liMaker = (text) => {
const li = document.createElement('li')
li.textContent = text
ul.appendChild(li)
}
form.addEventListener('submit', function (e) {
e.preventDefault()
itemsArray.push(input.value)
localStorage.setItem('items', JSON.stringify(itemsArray))
liMaker(input.value)
input.value = ''
})
data.forEach((item) => {
liMaker(item)
})
button.addEventListener('click', function () {
localStorage.clear()
while (ul.firstChild) {
ul.removeChild(ul.firstChild)
}
})
Here is the demo link of the small app.
The HTML5 Local Storage API (part of Web Storage) has excellent browser support and is being used in more and more applications. It has a simple API and certainly has its drawbacks, similar to cookies.
Over the past year or so I’ve come across quite a few tools and libraries that use the localStorage API so I’ve compiled many of them together into this post with some code examples and discussion of the features.
Lockr
Lockr is a wrapper for the localStorage API and lets you use a number of useful methods and features. For example, while localStorage is limited to storing only strings, Lockr lets you store different data types without the need to do the conversion yourself:
Other features include:
- Retrieve all key/value pairs with the
Lockr.get()
method - Compile all key/value pairs into an array with
Lockr.getAll()
- Delete all stored key/value pairs with
Lockr.flush()
- Add/remove values under a hash key using
Lockr.sadd
andLockr.srem
The Local Storage Bridge
A 1KB library to facilitate exchanging messages between tabs in the same browser, using localStorage as the communication channel. After including the library, here’s some sample code you might use:
// send a message
lsbridge.send('my-namespace', {
message: 'Hello world!'
});
// listen for messages
lsbridge.subscribe('my-namespace', function(data) {
console.log(data); // prints: 'Hello world!'
});
As shown, the send()
method creates and sends the message and the subscribe()
method lets you listen for the specified message. You can read more about the library in this blog post.
Barn
This library provides a Redis-like API with a “fast, atomic persistent storage layer” on top of localStorage. Below is an example code snippet taken from the repo’s README. It demonstrates many of the methods available.
var barn = new Barn(localStorage);
barn.set('key', 'val');
console.log(barn.get('key')); // val
barn.lpush('list', 'val1');
barn.lpush('list', 'val2');
console.log(barn.rpop('list')); // val1
console.log(barn.rpop('list')); // val2
barn.sadd('set', 'val1');
barn.sadd('set', 'val2');
barn.sadd('set', 'val3');
console.log(barn.smembers('set')); // ['val1', 'val2', 'val3']
barn.srem('set', 'val3');
console.log(barn.smembers('set')); // ['val1', 'val2']
Other features of the API include the ability to get ranges with start/end values, getting an array of items, and condensing the entire store of data to save space. The repo has a full reference of all the methods and what they do.
store.js
This is another wrapper, similar to Lockr, but this time provides deeper browser support via fallbacks. The README explains that “store.js uses localStorage when available, and falls back on the userData behavior in IE6 and IE7. No flash to slow down your page load. No cookies to fatten your network requests.”
The basic API is explained in comments in the following code:
// Store 'jslib.dev' in 'website'
store.set('website', 'jslib.dev');
// Get 'website'
store.get('website');
// Remove 'website'
store.remove('website');
// Clear all keys
store.clear();
In addition, there are some more advanced features:
// Store an object literal; uses JSON.stringify under the hood
store.set('website', {
name: 'jslib.dev',
loves: 'CSS'
});
// Get the stored object; uses JSON.parse under the hood
var website = store.get('website');
console.log(website.name + ' loves ' + website.loves);
// Get all stored values
console.log(store.getAll());
// Loop over all stored values
store.forEach(function(key, val) {
console.log(key, val);
});
The README on the GitHub repo has lots of details on depth of browser support and potential bugs and pitfalls to consider (e.g. the fact that some browsers don’t allow local storage in private mode).
lscache
lscache is another localStorage wrapper but with a few extra features. You can use it as a simple localStorage API or you can use the features that emulate Memcached, a memory object caching system.
lscache exposes the following methods, described in the comments in the code:
// set a greeting with a 2 minute expiration
lscache.set('greeting', 'Hello World!', 2);
// get and display the greeting
console.log(lscache.get('greeting'));
// remove the greeting
lscache.remove('greeting');
// flush the entire cache of items
lscache.flush();
// flush only expired items
lscache.flushExpired();
Like the previous library, this one also takes care of serialization, so you can store and retrieve objects:
lscache.set('website', {
'name': 'SitePoint',
'category': 'CSS'
}, 4);
// retrieve data from the object
console.log(lscache.get('website').name);
console.log(lscache.get('website').category);
And finally, lscache lets you partition data into “buckets”. Take a look at this code:
lscache.set('website', 'jslib.dev', 2);
console.log(lscache.get('website')); // 'jslib.dev'
lscache.setBucket('other');
console.log(lscache.get('website')); // null
lscache.resetBucket();
console.log(lscache.get('website')); // 'jslib.dev'
Notice how in the 2nd log, the result is null
. This is because I’ve set a custom bucket before logging the result. Once I’ve set a bucket, anything added to lscache before that point will not be accessible, even if I try to flush it. Only the items in the ‘other’ bucket are accessible or flushable. Then when I reset the bucket, I’m able to access my original data again.
secStore.js
secStore.js is a data storage API that adds an optional layer of security by means of the Stanford Javascript Crypto Library. secStore.js lets you choose your storage method: localStorage, sessionStorage, or cookies. To use secStore.js, you have to also include the aforementioned sjcl.js library.
Here is an example demonstrating how to save some data with the encrypt
option set to ‘true’:
var storage = new secStore;
var options = {
encrypt: true,
data: {
key: 'data goes here'
}
};
storage.set(options, function(err, results) {
if (err) throw err;
console.log(results);
});
Notice the set()
method being used, which passes in the options you specify (including the custom data) along with a callback function that lets you test the results. We can then use the get()
method to retrieve that data:
storage.get(options, function(err, results) {
if (err) throw err;
console.log(results.key); // Logs: "data goes here"
});
If you want to use session storage or cookies instead of local storage with secStore.js, you can define that in the options:
var options = {
encrypt: true,
storage: 'session', // or 'cookies'
data: {
key: 'data here'
}
};
localForage
This library, built by Mozilla, gives you a simple localStorage-like API, but uses asynchronous storage via IndexedDB or WebSQL. The API is exactly the same as localStorage (getItem()
, setItem()
, etc), except its API is asynchronous and the syntax requires callbacks to be used.
So for example, whether you set or get a value, you won’t get a return value but you can deal with the data that’s passed to the callback function, and (optionally) deal with errors:
localforage.setItem('key', 'value', function(err, value) {
console.log(value);
});
localforage.getItem('key', function(err, value) {
if (err) {
console.error('error message...');
} else {
console.log(value);
}
});
Some other points on localForage:
- Supports use of JavaScript promises
- Like other libraries, not limited just to storing strings but you can set and get objects
- Lets you set database information using a
config()
method
Basil.js
Basil.js is described as a unified localStorage, sessionStorage, and cookie API and it includes some unique and very easy to use features. The basic methods can be used as shown here:
basil = new Basil(options);
basil.set('foo', 'bar');
basil.get('foo');
basil.remove('foo');
basil.reset();
You can also use Basil.js to test if localStorage if available:
basil.check('local'); // returns Boolean value
Basil.js also lets you use cookies or sessionStorage:
basil.cookie.set(key, value, {
'expireDays': days, 'domain': 'mydomain.com'
});
basil.cookie.get(key);
basil.sessionStorage.set(key, value);
basil.sessionStorage.get(key);
Finally, in an options
object, you can define the following with an options
object:
- Namespaces for different parts of your data
- Priority order for which storage method to use
- The default storage method
- An expire date for cookies.
options = {
namespace: 'foo',
storages: ['cookie', 'local'],
storage: 'cookie',
expireDays: 31
};
lz-string
The lz-string utility lets you store large amounts of data in localStorage by using compression and it’s pretty straightforward to use. After including the library on your page, you can do the following:
var string = 'A string to test our compression.';
console.log(string.length); // 33
var compressed = LZString.compress(string);
console.log(compressed.length); // 18
string = LZString.decompress(compressed);
Notice the use of the compress()
and decompress()
methods. The comments in the above code show the length values before and after compression. You can see how beneficial this would be seeing how client side storage always has limited space.
As explained in the library’s docs, there are options to compress data to Uint8Array (a new-ish data type in JavaScript) and even the ability to compress the data for storage outside of the client.
secure-ls
Secure localStorage data with high level of encryption and data compression.
Features
- Secure data with various types of encryption including
AES
,DES
,Rabbit
andRC4
. (defaults toBase64
encoding). - Compress data before storing it to
localStorage
to save extra bytes (defaults totrue
). - Advanced API wrapper over
localStorage
API, providing other basic utilities. - Save data in multiple keys inside
localStorage
andsecure-ls
will always remember it’s creation.
Libraries used
- Encryption / Decryption using The Cipher AlgorithmsIt requires secret-key for encrypting and decrypting data securely. If custom secret-key is provided as mentioned below in APIs, then the library will pick that otherwise it will generate yet another very
secure
unique password key using PBKDF2, which will be further used for future API requests.PBKDF2
is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can’t be used directly as a cryptographic key, some processing is required.A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.Eg:55e8f5585789191d350329b9ebcf2b11
anddb51d35aad96610683d5a40a70b20c39
.For the generation of such strings,secretPhrase
is being used and can be found in code easily but that won’t make it unsecure,PBKDF2
‘s layer on top of that will handle security. - Compresion / Decompression using lz-string
Example 1: With default
settings i.e. Base64
Encoding and Data Compression
var ls = new SecureLS();
ls.set('key1', {data: 'test'}); // set key1
ls.get('key1'); // print data
{data: 'test'}
Example 2: With AES
Encryption and Data Compression
var ls = new SecureLS({encodingType: 'aes'});
ls.set('key1', {data: 'test'}); // set key1
ls.get('key1'); // print data
{data: 'test'}
ls.set('key2', [1, 2, 3]); // set another key
ls.getAllKeys(); // get all keys
["key1", "key2"]
ls.removeAll(); // remove all keys
Example 3: With RC4
Encryption but no Data Compression
var ls = new SecureLS({encodingType: 'rc4', isCompression: false});
ls.set('key1', {data: 'test'}); // set key1
ls.get('key1'); // print data
{data: 'test'}
ls.set('key2', [1, 2, 3]); // set another key
ls.getAllKeys(); // get all keys
["key1", "key2"]
ls.removeAll(); // remove all keys
Example 3: With RC4
Encryption but no Data Compression
var ls = new SecureLS({encodingType: 'des', isCompression: false,
encryptionSecret: 'my-secret-key'});
ls.set('key1', {data: 'test'}); // set key1
ls.get('key1'); // print data
{data: 'test'}
ls.set('key2', [1, 2, 3]); // set another key
ls.getAllKeys(); // get all keys
["key1", "key2"]
ls.removeAll(); // remove all keys
Know any others?
If you’ve built something on top of the localStorage API or a related tool that enhances client-side storage, feel free to let us know about it in the comments.