Get started with Service Workers

Kevin Hu
10 min readOct 29, 2020

Disclosure: I work for and have a stake in The East Harlem Software Company, Inc and we deliver a web application called Care Team that stores protocols for physicians to access on-the-go. This article discusses this use case and is therefore influenced by this business case.

Wondering how to deliver an offline experience to users of your web application? Don’t know what I’m talking about? This article is for you.

If you’re not sure why anyone want to build a web application that can be accessed via any browser, be installed onto any device, and is usable without an internet connection; try one of the following:

Companies that have launched Progressive Web Apps have seen impressive results. For example, Twitter saw a 65% increase in pages per session, 75% more Tweets, and a 20% decrease in bounce rate, all while reducing the size of their app by over 97%. After switching to a PWA, Nikkei saw 2.3 times more organic traffic, 58% more subscriptions, and 49% more daily active users. Hulu replaced their platform-specific desktop experience with a Progressive Web App and saw a 27% increase in return visits.

  • consider the case a little further down

In all seriousness, excellent articles and resources from MDN, Google, and others do exist on the topic of service workers and their use in progressive web apps. This article should serve as a gentle introduction for the uninitiated.

Prerequisites

The rest of this article is written assuming proficiency with HTML, CSS, JavaScript, ES6 arrow functions (es6-features, MDN), and Promise-based APIs with .then and .catch as well as your favorite suite of browser dev tools.

Terminology

Just want to get some vocabulary words out of the way. For the purposes of this article:

  • Progressive web application = mobile-optimized web application; application that installs on any device and is usable without an internet connection; what we’re trying to build.
  • Service workers = the main subject of this article; the candidate W3C recommendation here; introduced here by MDN, and here by Google; some JavaScript that runs in the browser alongside your “main application code” , caches responses to requests, will try to respond to fetch, sync, and push events.
  • Dev tools = Chrome Dev Tools; Firefox Developer Tools and Safari Web Inspector also work, but I do not have experience with them.
  • Resource= the data that web apps usually serve from a server packaged in an HTTP response object; can be cached and served from the browser cache by browsers running Service Workers.

Objectives

After you are done reading and building out the demo application on your own, you should feel capable of:

  • Explaining to a fellow dev the role of a Service Worker in delivering an offline experience.
  • Registering and writing your own “proof-of-concept” Service worker.
  • Explaining why you would rather use generated Service Workers in the future with tools like Workbox.

Case

Your users are health care workers on the frontlines and are constantly on the go, traveling to peoples homes, sometimes in remote areas, other times just roaming about the hospital or clinic and treating someone where the the wi-fi signal is weak. The application you are building is a wiki that holds institutional quick reference articles and protocols for point-of-care access. These hardworking professionals need access to their data — patient care cannot wait for wi-fi.

Native app solution

A native app could download all or some subset of the documents your users are most likely to use based on search history, most frequently accessed, or even clinical role and will keep these available on the user’s device so that when they open up the app without internet, they can still navigate to and read these documents.

But native apps are hard to make available across all devices — maybe you can only support iOS and half of your have an Android phone, but your favorite productivity app is not available in the other ecosystem.

Mobile optimized app solution

You could write a web application, which would be accessible from any browser. But unlike a traditional web application — which requires an internet connection to work — your mobile optimized application, like a native app, also stores some documents on the user’s device for access while offline.

Where though? In a browser’s cache. Using what? Service workers.

Service Workers

Overall strategy

Service workers are JavaScript processes that run in your users’ browsers. To be used in an application they must be registered by your main application code, installed successfully, and written to intercept and respond to requests with data in the browser’s cache. You may hear it being referred to as a client-side proxy server. This prevents doomed network requests from going out when your users have no access to internet.

Flow chart depicting simplified service worker workflow

Consider the flow chart above. To take advantage of a service worker, your application calls navigator.serviceWorker.register to set off the installation process. The service worker process then starts. It must handle the install Event by trying to throw some files into the browser cache. The worker will make requests to the server for this purpose. Assuming this is successful, the install Event completes.

Some time later, your user loses connection to the internet, but needs access to the resource or document located at the “/SARS-CoV-2-testing-protocol” path (or maybe an image, css file, or some other resource). Thankfully a service worker was installed on their browser. When the request goes out, a fetch Event is fired and passed to the service worker before being sent out over the network. The service worker runs a match to see if it has a cached response. Good thing it does, and it is able to respondWith its cached version. There you go — resource delivered, no internet required — the offline experience.

Let’s Code

Enough with the high level shenanigans. Time to write your own proof-of-concept Service Worker. Here are the steps again:

  • Registration- your application JavaScript does this to tell the browser to use your service worker. This step runs every time someone visits your page and runs your application JavaScript.
  • Install- an install event fires upon successful registration. Your Service Worker should have registered an event handler for the install Event. The event handler should open a cache and throw some data in there. This step runs every time someone visits your page and a registration event occurs. Thankfully, we usually use a method here in the event handler called open, which basically implements a kind of “find or create” protocol and only creates a new cache if one does not already exist.
  • Fetch- the user requests a resource and your Service Worker handles it by checking to see if it has the resource in the cache (calling match). If the match method returns with something, then the Service Worker runs a respondWith — responding with the requested resource.

Set up your own project with a structure similar to the below or pull from the demo repo here. The cached resource in this demo is the icon.png. Our Service Worker is sw.js . The index.html and index.js make up our main application code.

.
├── icon.png
├── index.html
├── index.js
└── sw.js

In this directory, run a development server with something likehttp-server -c-1 and check out your web app at localhost:8080. The -c-1flag is useful for disabling HTTP caching — which is helpful for learning purposes (aside: read some more about HTTP caching to dig deeper into this point). If you don’t have an HTTP server of choice, feel free to install one globally, e.g. npm install --global http-server .

Visit the browser page with Chrome Dev Tools and notice in the Network tab the little gear icon with sw.js:

and a console log message ServiceWorker registration successful with scope: http://localhost:8080/ .

index.html is served, and then sources the script index.js which has:

index.js Gist

The key here is to call navigator.serviceWorker.register(PATH_TO_SERVICE_WORKER_FILE) which is what tells the browser to fetch the Service Worker located at /sw.js in this case.

Once fetched, the Service Worker file then runs and registers event handlers for the “install” and “fetch” events. Install fires upon successful registration, calls open on the globally available caches object, and calls addAll with an array of URLs (in this case urlsToCache [“/”, “/icon.png”])— the responses of which are then retrieved from the server and put into the opened cache.

Fetch fires whenever the browser requests any resource — in this case, possible resources are localhost:8080/ /icon.png /index.js /sw.js . The fetch event handling function calls respondWith and then retrieves a match which is then checked to see whether it is actually defined. If so, it just returns the response. If not, it returns the result of a fetch API call to the server for a valid response.

Let’s review:

waitUntil is specific to Extendable Events, a type of Event that is based on Event and only applicable to the ServiceWorkerGlobalScope.

waitUntil literally extends the lifetime of the install event (recall the initial firing occurred upon completion of registration) so that the event does not complete firing until the resolution of a Promise passed as the parameter to waitUntil.

caches is a global that gives us access to the CacheStorage interface. See [MDN on CacheStorage] and more [MDN on the caches global property].

CacheStorage.open - Takes a CACHE_NAME argument and returns a Promise that resolves to the requested Cache object (it actually performs a kind of find or create type operation).

Cache.addAll- takes an array of URLs, retrieves them, and adds the resulting response objects to the given Cache. The request objects created during retrieval become keys to the stored response operations. [Source MDN].

What would normally have gone out over the network as an AJAX request can now be handled using our service worker cache. It runs a simple match call on our CacheStorage interface and returns the cached response. If there is no match found, the service worker file calls fetch and returns whatever it grabs from the network (OR the HTTP cache if that is being used).

Try not writing your own Service Workers

So you probably noticed that there was a lot of code for apparently very little logic. And we did not even touch on the fundamental questions of:

  • Should we serve from Cache always, from server always, or something in between?
  • How do we clear stale resources and replacement with new ones when we want to?

One could write all of that logic on one’s own. Or one could use a tool like Workbox, which provides an API that generates the appropriate service workers for your application. Learn from the lessons of the Google Bulletin team:

Avoid writing a service worker script by hand. Writing service workers by hand requires manually managing cached resources and rewriting logic that is common to most service workers libraries, such as Workbox.

Getting from understanding Service Worker principles to delivering an offline experience is not easy. It helps to return to our case. Our application might consist of some scaffolding HTML, CSS, JS; and then some protocols that might be revised every few days, weeks, or even years (just depends). Our users will usually want the most up-to-date protocols and should count on being able to get these should a network connection be available. So how do we write a service worker that meets these needs?

First summarize — it sounds like we have some basic scaffolding resources, like HTML, CSS, JS, and any images that are part of the landing page or basic application interface that should always be served from the cache first — maybe only from the cache ever.

Then we have these protocols that should probably always be requested from the network if we have a connection available so we get an up-to-date version, BUT we can use the cache if the user is offline. Often, our user may know that the protocol has not been updated since they last looked at it and would be happy to refer to the offline version.

Once you have outlined what resources you want to serve, how you want requests for them to be handled (e.g. cache only, cache first, network first with fallback to cache), and have realized that you do not want to rewrite these caching strategies from scratch, then you have you have gotten as far as this article can take you. Time to summarize again and take the next steps.

Summary

Recap

  • The role of a service worker is to run alongside your main application, implementing whatever type of caching strategy you would like, storing files in the browser, intercepting requests like a proxy server, and serving cached files when appropriate.
  • Follow the tutorial to build a minimal proof-of-concept service worker.
  • Try not to build your own service worker (unless it is for learning purposes or you just have that special strategy that no one else has thought of before). Tools like Workbox simplify implementing service

Next Steps

  • Still not feeling this whole “offline” experience thing? Try this article on for size.
  • Get more review on the Service Worker life cycle at MDN and Google.
  • Realize that Service Workers only work well with a cohesive caching strategy that also takes into account HTTP caching. Your first step here should be to read this Offline Cookbook from 2014.
  • Next: Jake Archibald defines the problem well in this great article. For an excellent visual breakdown and a table of what types of resources and user needs might match with which types of service worker logic, check out this great overview by Jon Chen.
  • Check out tools like Workbox that will generate service worker logic on your behalf with a simpler API than writing your own from scratch like we did here.

--

--