Back-end developers must store data. There are many options for how to store data. Data storage doesn’t just exist at the back. There are many ways to store data on the browser’s front-end. This storage can be used to improve application performance and save preferences. It also allows us to keep our applications state across different sessions or computers.
This article will discuss the various ways that data can be stored in the browser. To help you understand the pros and cons of each storage option, we will discuss three scenarios. You will then be able decide which storage option is best for you. Let’s get started!
LocalStorage has been a popular option in browsers and is a go-to choice for many developers. It is accessible for every page under the same domain and protocol. The storage is restricted to 5MB.
Surprisingly the Google Chrome team does not recommend this option. It blocks the main thread, and it isn’t accessible to web workers or service workers. Although they tried KV Storage as an improved version, it didn’t work out and is still in beta.
Window.localStorage can only save UTF-16 strings. Before saving data into
localStorage, it is important to ensure that the data has been converted to strings. These are the three main functions:
These are all synchronous which makes them easy to use, but also blocks the main thread.
It is worth noting that
localStorage also has an identical twin,
sessionStorage. Only the difference between sessionStorage and is that the data will only last for the current session. However, the API remains the same.
Let’s take a look at it. This first example shows how
localStorage can be used to store the user’s preferences. It’s a property in boolean that controls the brightness of the site.
To see if the state has been saved between sessions, you can click the checkbox to refresh the page and then check again. To see the
load functions, and to learn how to convert it to strings and parse it. We can only store strings.
The second example loads the PokeAPI Pokemon names.
Sending a GET request with
fetch, we list all names in a
ul elements. We cache the response in our localStorage to make it easier for us to visit again or work offline. To read the cache, we need
JSON. Stringify converts the data into string.
This last example shows how the user could browse different Pokemon pages and save the page for future visits.
localStorage. The problem is that the state of the page is not saved to the internet. This behaviour prevents us from sharing the page we want with our friends. We will learn how we can overcome this problem later.
These three items will be used in our next storage solutions. The Pens were forked and the functions have been changed. All methods have the same skeleton.
IndexedDB provides a browser-based storage option. You can store large amounts of structured data, including files and blobs. IndexedDB, like all databases, indexes data to make it easy for queries to run efficiently. IndexedDB is more difficult to use. It is necessary to set up a database and tables as well as use transactions.
IndexedDB is a more complex language than
localStorage. The native API is used in the example, with the Promise wrapper. However, third-party libraries are highly recommended to aid you. LocalForage is my recommendation. It uses the same
localStorage API, but it implements it using a progressive enhancement method. If your browser does not support IndexedDB it will still use
Let’s get coded and move on to the example of user preferences.
idb, the Promise wrapper we use in place of using a low-level event-based API. These two are almost identical so do not be alarmed. First, notice that all database accesses are async. This means we don’t stop the main thread. This is an important advantage over
To make our database available to the entire app, we need to create a connection. Our database is given a name
my_db. It also has a schema version of 1, as well as an update function that allows us to make changes between versions. It is similar to database migrations. The database schema we have is very simple. It only has one object store,
preference. A SQL table is equivalent to an object store. Transactions are required to write and read data from the database. IndexedDB’s most tedious aspect is using transactions. Take a look at IndexedDB’s
IndexedDB is more complicated than
localStorage. It might be more practical to use
localStorage for key-value cases.
IndexedDB is a stronghold for application data such as the Pokemon one. This database can hold hundreds of gigabytes or more. IndexedDB can hold all your Pokemon and make them accessible offline. They even have the ability to be indexed. It is the best option for app data storage.
The third example was skipped because IndexedDB isn’t making any different to
localStorage. The user can still bookmark the page or share it with others, even with IndexedDB. Both are not appropriate for this particular use case.
Cookies are a special storage method. This is the only storage option that can be shared with the server. Every request includes cookies. This can happen when the user navigates our website or sends Ajax queries. It allows us to share the state of the client with the server and between applications within different subdomains. Other storage options, such as those described here, do not allow this. Cookies are included with each request. This means we need to make sure our cookies don’t exceed the requested size.
LocalStorage. Cookies are combined into one semicolon separated string and sent with the request’s cookie header. Each cookie can have many attributes, including expiration and allowed domains.
The examples show you how I manipulate cookies on the client side, however, it is also possible to modify them on the server side.
If the cookie is able to be used by the server, saving the preferences of the user can work well. For example, in the theme use case, the server can deliver the relevant CSS file and reduce potential bundle size (in case we’re doing server-side-rendering). This could be used to distribute preferences between multiple apps in a subdomain without the need for a database.
cookie.cookie to save the new cookie. See the
save function above. To ensure that it doesn’t expire after the tab closes, I add the
dark_theme cookies to the
max_age attribute. The
Secure attribute are also added. They are required because CodePen runs the examples in an iframe, however they will be unnecessary for most situations. Parsing the cookie string is required to read a cookie.
This is how a cookie string looks:
First, split the string using a semicolon. We now have an array with cookies in
key1=value1. Now we must find the correct element within the array. We divide the array by the equal sign to get the final element. Although it is a bit cumbersome, once you have implemented the
GetCookie function or copied it from my example:P you will be able to forget about it.
It is a bad idea to save application data in cookies. This will increase request sizes and reduce the application’s performance. The server can’t benefit because it is a duplicate of information that it has already in its database. Cookies should be kept small if you are using them.
Also, the pagination example doesn’t work well with cookies. We want to share the current page with other people, but it is only temporary.
URL isn’t a storage per se but it can be used to share a state. It is simply adding query parameters that allow you to create the state of the URL. Search queries and filters are the best examples. If we search the term
flexbox on CSS-Tricks, the URL will be updated to
https://css-tricks.com/?s=flexbox. It is so easy to share your search queries once you use the URL. You can also hit the refresh button for newer search results or bookmark your query.
Strings can only be saved in URL, as the URL’s maximum length is very limited. Our state will need to be kept small. Long URLs are intimidating and not what people like.
CodePen runs the example using iframe, which means that you can’t see the URL changing. Don’t worry, all bits and pieces of CodePen are available for you to use wherever you like.
We can access the query string through
window.location.search and, lucky us, it can be parsed using the
URLSearchParams class. There is no need for complex string parsing. The
get function can be used to retrieve the current value. set is the function that allows us to create . You can’t just set the value, you also have to change the URL. This can be done using
history.replaceState, depending on the behavior we want to accomplish.
As we have to include this information to all URLs, I would not recommend that users save their preferences. We cannot guarantee the state of the URLs the user visit.
We don’t have enough space to save any application data, just like cookies. Even if it were possible to save the data, the URL would be too long to make it easy to click. This could look suspiciously like a phishing attempt.
The temporary application state, just like in our pagination example is best for the URL query strings. You cannot actually see URL changes but you can update the URL with the
page=x query parameters every time you visit a webpage. It searches for the query parameter when the page loads and retrieves the correct page. This URL can now be shared with friends to allow them to enjoy their favorite Pokemon.
The network storage is called cache API. This is used for caching network requests and responses. Cache API is perfect for service workers. The Cache API allows service workers to intercept all network requests. They can also cache them using the Cache API. A service worker can return an already cached item to the network as a response, instead of retrieving it from the server. This will reduce the network load time and allow your application to work offline. Although originally designed for service workers, the Cache API can be used in all modern browsers. This API is extremely powerful and can greatly improve the user experience of your application.
The Cache API storage space is unlimited, just like IndexedDB. You can store hundreds or even thousands of megabytes, if needed. It is not synchronous, so your main thread will not be impacted by the API. It’s also accessible via the global property
Chris made an amazing pen with the practical example of using both service workers and Cache API.
Extra Bonus: You can extend your browser
You have an alternative way to store data if you create a browser extension. It was discovered while I was working on daily.dev. You can access it via
browser.storage if you are using Mozilla’s polyfill. To gain access, make sure you request storage permissions in your manifest.
You have two options for storage: local or sync. Local storage can be explained by saying that it’s not shared or kept locally. The sync storage can be synchronized as part of your Google account. This storage will sync with any extension you use. Pretty cool feature if you ask me. They both share the same API, so switching back and forth is super simple if you need to. Async storage means it does not block the main thread
localStorage. This storage option requires an extension to your browser. However, it is very simple and works almost exactly like
localStorage. contains more details about how it works.
There are many storage options available in the browser to save our data. IndexedDB should be our primary storage, according to the recommendations of the Chrome team. This is async storage that has enough room to hold everything we need.
localStorage should not be used, however it is more convenient than IndexedDB. While cookies are great for sharing client information with the server, they are predominantly used to authenticate clients.
To create shareable pages, such as search pages or other social media sites, you can use the URL query string to save this data. Finally, if an extension is built, be sure to check out