Offline Web Apps: Using Local Storage and Service Workers.

Onejohi
8 min readJul 13, 2018

In this article, I’ll explain to you ways in which you can successfully run your website offline almost like an installed app.

To create an offline website, you’ll need to understand two fundamental web topics, service workers (sw) and browser storage. Let’s start with browser storage.

Storing data in the browser.

This is a double edged sword in some sort. Storing data in browsers has it’s pro’s and cons. Since web storage was introduced in HTML5, older browsers have limited support though plugins can be installed to make the browser compatible with the new technology. Before HTML5, application data had to be stored in cookies which had to be transferred between the client and server and had a very limited storage capacity (4095 bytes) as opposed to local-storage's 5mb (5,875,280 bytes).

Local Storage data is persistent, unlike cookies which have an expiry date. To clear data from local storage, you’d be forced to manually clear the data from the application tab in your browser developer tools. However, your PC can automatically (meaning without your consent or permission) delete data from local storage when it’s running out of file space to make room for more files.

Local Storage is the simplest way to store key-value pairs of data on the browser. Other methods are using IndexedDB (A tutorial on this in the making), WebSQL (Deprecated, not advisable to learn or use), File System API and Application cache. In this tutorial, I will show you how to use Local Storage. For the more complex and powerful IndexedDB, keep in touch for the next article I’ll release on the topic.

Working with Local Storage.

To test whether your browser supports local storage API, run this JavaScript code to confirm.

(function() {
if(window.localStorage)
console.log("Local Storage Supported")
else
console.log("Local Storage Not Supported")
})();

This self running function should print “Local Storage Supported” to your console. This means you can now start storing and retrieving data from your local storage.

Since local storage is designed to work with key-value pairs, you’re required to set the key of the data before adding it. The localStorage object provides a couple of methods and properties that help us run it.

window.localStorage.setItem(“key”,”value”): This method accepts two arguments. The first is the key you’d like to set on your value and the second is the actual value. If you’d want to save a username, you can use username as the key and store any value with it similar to how you assign variables data.

window.localStorage.getItem(“key”): This method accepts one argument which is the key that’s attached to the value. This returns the value of the key used. For instance if you’d want to know the username stored, replace the work key with username in the statement above to get the value.

window.localStorage.removeItem(“key”): This method deletes the value of the key along with the key itself. It leaves no evidence that the value existed nor the key. You can remove username information by passing username as an argument.

window.localStorage.clear(): This method expects no arguments. It deletes all content stored in local storage but limit to the origin of the application. Therefore, if theoretically Facebook and Twitter store some data in your browser, when you use this method on Facebook’s page, your Twitter data won’t be affected but all of Facebook’s data will be gone (But is it really your data??).

window.localStorage.key(n): This method accepts a numerical argument that return’s the nth key in the storage. Since the order of the keys is user-agent defined (browser specific), it’s highly discouraged to use it as results will vary from browser to browser stored with the same data.

window.localStorage.length: This property works similar to arrays. It simply returns the number of objects stored locally. The returned value will always be of type number.

Here’s a simple code snippet that I created that gets the username using a prompt and the age and saves the information in local storage, displays number of items stored and print’s back the information to the console window.

(function() {
//check to see if local storage is supported
if(window.localStorage)
console.log("Local Storage Supported")
else
console.log("Local Storage not supported in browser")
//prompt user for username and age
let username = prompt("Enter username:","")
let age = prompt("Enter age:","")
//add username and age to localstorage
window.localStorage.setItem("username", username)
window.localStorage.setItem("age", age)
//show number of objects stored
console.log(window.localStorage.length)
//get items from local storage
console.log(window.localStorage.getItem("username"))
console.log(window.localStorage.getItem("age"))
})();

To view data stored in your browser, launch developer tools from your favorite browser using F12 or right click and choose the Element Inspector or Inspector option. Once the developer tools are launched, go to the Application tab to view application details.

You ought to be viewing something similar. My app is hosted on localhost:8081. The tool I’m using requires NodeJS installed but you can use any kind of tool you prefer like WAMPP, XAMPP… etc. To test on data persistence, you can close your browser and relaunch it. Heck! Shut down your computer if you must, the data will still be there. Even a year later.

Something worth noting is data in local storage is stored as strings except when explicitly set to numbers. Arrays and Objects need to be converted from strings. Web Storage (localStorage, sessionStorage, indexedDB) implements a same origin security protocol. This means all pages from the same origin can access the data but not of a different origin. To explain this, let’s use Facebook again. Essentially, https://facebook.com/users can store data that can be consumed and manipulated by https://facebook.com/pages but the data cannot be accessed nor manipulated by https://twitter.com because Twitter has a different origin from Facebook. All of Facebook’s pages can access this data.

Why isn’t my app working when the internet is down??

Well, prolly because you’re not using service workers. A service worker’s primary function is to intercept http calls made by pages and returning cached files. To make your app run offline, you’ll have to cache ALL urls that serve application logic files. In our case, that’s just the .js file and .html file.

A service worker runs separate from your website. It’s completely independent off the pages and can only interact with them after installation. Since service workers are a more complicated topic, let’s focus on the part that cache’s the application.

Working with the Service Worker.

To install a service worker, you have to start by registering the service worker. Create a sw.js file next to your .js file and paste in the following code.

if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js')
.then((registration) => {
console.log("Service Worker registration successful")
}, (err) => {
console.log("Registration failed")
})
})
}

To cache our data, we need to specify a version so when we update the service worker, we can specify what new things need to be cached in the new version and what needs to be dumped. Paste in the following code.

let cache_name = 'mysite-v1'let urls_to_cache = [
'/',
'/js/scripts.js'
]
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(cache_name).then((cache) => {
return cache.addAll(urls_to_cache)
}) )
})

This block of code listens for the successful installation of the service worker (self) to then begin caching files. Once the event is captured, you can use it by creating a variable e and attaching it to it. The instance then exposes the method .waitUntil() which takes a promise and confirms whether installation succeeds or not, then caches are opened using the caches.open() function that accepts and argument of cache name. Once all the files are cached, your service worker will install, but if even one of them fails, the service worker will fail to install.

Now that you’ve installed the service worker, you’d want to return your cached data.

Once a service worker is installed, the user may start nagivating through the pages and this actions will prompt fetch requests to be made. As I explained earlier, service workers listen to these requests and hijack/intercept them before they’re made to check if the data is available locally. Here’s the code that does that.

self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request).then((response) => {
if(response)
return response
else
return fetch(e.request)
}) )
})

This block of code listens for fetch events and respond with caches that match the requests in the fetch events. If a particular request matches files in cache, it’s cached version is served instead, if it doesn’t then the service worker fetches the request instead. That’s it!

Voila! It’s an offline app!

You’ve successfully made your service worker. Remember to include it in your html file when including script files. Your service worker will serve cached files when the app is offline and data stored in the app is persistent, whether online or offline. This ensure’s your app can run without an internet connection. Make sure your sw.js is in the same folder as your index.html to prevent a (404) resource not found error when the service worker is listing resources to cache. It automatically assumes the folder it’s placed in is the root folder.

This is all you would need to make a simple offline application. Note we didn’t use any frameworks or plugins, just pure JavaScript. If you’d like to view the app (and more examples), you can create a pull request on GitHub using the repository link provided below.

https://github.com/onejohi/offline-webapp.git

notice the files fetched from ServiceWorker when offline checkbox is true.

Try one of the following. In the developer tools, open the Network tab and check the offline checkbox to simulate an offline experience. Alternatively, turn off your WiFi or disconnect your Ethernet cable to ensure there’s no internet. Refresh your app and you’ll see your app being served from the cached version. Shut down your browser, reopen it (while still offline) and check the URL where your web-app was running. I’m 100% sure your app will reload as if you’re still connected to the internet.

Conclusion.

Service workers are powerful. They’re not limited to caching requests, you can perform push notifications and periodic background syncs using service workers. Also, Web Storage is not limited to local-storage, the reason why I chose to use it was to make the article short as longer articles cause readers to get bored and confused at most times, I simply chose the simplest way to store data on the browser.

Using these two powerful tools, you can create powerful, complex web applications. Thank you for checking out this article and I hope it’s helped you understand service workers a little and local storage fully. More information about service workers will be found among my work in future, Cheers!

--

--

Onejohi

Creative 🚀 | Gamer 🎮 | Web & App developer 💻📱 | Graphics Designer 📏📝 | Entrepreneur 💶 | Cool AF 😎🤓 https://onejohi.com | WhatsApp +254727321766