Getting Started with Service Workers
Tags: opensource, sw, serviceworker, javascript
Published: 2017-03-12 19:24:51

---
title: Getting Started with Service Workers
published: true
description: Acting as a proxy between browser and network, Service Workers help developers build  'offline first' web applications.
cover_image: 
tags: oss, sw, service-worker, ServiceWorker, js, JavaScript
---

## What is a Service Worker?


A Service Worker can be thought of as a JavaScript file that runs in the context of  [Shared Worker ](https://developer.mozilla.org/en/docs/Web/API/SharedWorker). It acts as a proxy between the browser and network. 

Service Workers help developers build  `offline first` web applications, what we meant by offline first is even if there is no/slow internet connection on user's device in those scenarios also, we will be able to serve our app, or something, to user which is at par or better than native apps that work offline. 

Service Workers run in a different thread than normal JavaScript thread, hence all communication between the Service Worker thread and main thread happen through messages sent via [`postMessage`](https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage).  One of the less talked features is that it is completely asynchronous and all synchronous web APIs won't go well with it.

Service Workers sits between our browser and network, so in order to prevent our web app from middle man attacks, Service Workers run only on HTTPS [ localhost is an exception ].


Service Workers make heavy use of modern web constructs, listed below :

 - [Promise API](http://www.html5rocks.com/en/tutorials/es6/promises/)
 - [Fetch API](https://developers.google.com/web/updates/2015/03/introduction-to-fetch)
 - [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache)  
 - Chrome dev tools features

It is recommended that you get familiar with above APIs before going further. 

The Service Worker API is still a new feature in web, so is supported (or partially supported) by few browsers, you can check compatibility [here](https://jakearchibald.github.io/isserviceworkerready/).

## Registration

In order to be able to use Service Workers in our app, we have to first register it. Registering a Service Worker is nice and easy.

First, we'll detect if our browser supports Service Worker or not.
 
```js
// check if Service Worker support exists in browser or not
if( 'serviceWorker' in navigator ) {
	//Service Worker support exists
} else {
	//still not supported
}
```

Now that we have detected if Service Worker support exists or not in the browser, it's time to register it.

The below code registers a Service Worker present in `service-worker.js` file which has the scope of the whole of our application.

```javascript
// check if Sevice Worker support exist in browser or not
if( 'serviceWorker' in navigator ) {
	navigator.serviceWorker  
					   .register( 'service-worker.js' , { scope : ' ' } )  
					   .then( function( ) { 
						   console.log('Congratulations!!Service Worker Registered');
					   })
					   .catch( function( err) {
						   console.log(`Aagh! Some kind of Error :- ${err}`);
					   });
} else {
	//still not supported
}
```

In the above code snippet

- We are detecting if Service Worker support exists in our browser or not.
- If support exists, we are registering a file named `service-worker.js`(You can name your file anything). Note that, ideally `service-worker.js` file should be in the root directory. If our Service Worker file is present in some other directory like `directory/service-worker.js` then Service Worker will get in action only when something from `directory/` is being fetched.
- We are passing an object as the second argument to `register()` function which has a scope key whose value is ' ', that means we want to register Service worker in the scope of the current directory. You can also manually set Service Worker scope to any directory of your choice.
- Finally, since whole Service worker is 'promise based', we are catching if Service Worker is registered or not.

_If you don't pass anything as the second argument to `register()` function then Service Worker is registered against the directory in which Service Worker file is present._


**Now registration of Service Worker is complete.**

Let's dig in a bit deeper. As we discussed earlier that Service Worker communicate with main thread using `messages/events`. Whenever we do a network fetch, the network request goes through the Service Worker and triggers a `fetch event`. 

Now in our `service-worker.js` let's add a listener for this event.

```javascript
self.addEventListener( "fetch" , function (event) {
	//fetch request as specified by event object 
	console.log(event.request); //Note that Request and Response are also objects 
});
```

Above code does nothing fancy. It simply logs out `Request object` to browser console.

Once we have the request object, we could intercept this request the way we wanted it to be. We'll do all this heroics later.

Seems easy till now? Ok, let's move to hardest, but most important, part.


## The Service Worker LifeCycle 

If we try to run above example in our browser and look at the console, we see below message 

`Congratulations!! Service Worker Registered` 

But this message is for **first time load** only and when we **refresh our page again then we get all request objects printed in console**. 

This behavior is due to the fact that when we first loaded the page there was no Service Worker registered so the listener for `fetch` event can't be triggered, so at the first, we could only see Service Worker being registered.

Now when we refreshed the page, a new web client is spawned and it saw that there is already a Service Worker registered against this domain so `fetch` event listener comes to action and we could see request objects printed in the browser console.

Now if you try to change something in your `fetch` event listener, let's suppose we change to this:- 

```javascript
self.addEventListener( "fetch" , function (event) {
	//fetch request from event object 
	console.log("Resource requested is :- ",event.request.url);
});
```

And now if we refresh the page we could not see any change in the console. **Seems weird???**

The main culprit for this kind of sin is the Service Worker lifecycle.

A Service Worker has a life cycle which consists of following stages:

* Install
* Activate
* Wait
* Terminated/Redundant


Let's analyze above scenario where we changed something and it was not reflected when we refreshed the page.

In this case, we already had an old Service Worker registered for the page in `active` state and when we refreshed the page, browser saw that the Service Worker file has been updated. It `installs` new Service worker but keeps this new one in `waiting state` because old one has not been `terminated`. The old one can only be terminated by closing all pages that reference it or by navigating to other domain over which this Service Worker has no control. If we terminate our Service Worker by the above-mentioned methods and again head over to our page we should see the changes in effect because now no active page was referencing an old Service Worker so new ones becomes `active` and old one becomes `redundant`. As soon as the new Service Worker takes control, it will fire an `activate` event.

Now, how, as a developer, do we know which state is our Service Worker in and how can we manually alter lifecycle to make our development easy.

Here comes Chrome Dev Tools to rescue. 

In chrome dev tools [Can be opened by pressing F12 or CTRL+SHIFT+I or Ctrl+Shift+J].  Head over to `Application` panel and we can see a separate resource written as `Service Workers`. 

Here, we can see in which state we are in. We can also manually unregister, update & stop any Service Worker and we can also see when one was received.

![](https://cdn.scotch.io/1925/deRKdGFzScmthGVyv8h7_Capture.JPG)

There is more to it.

 Suppose we as a developer want that, every time we reload/change something in our page new Service Worker should be active without going to waiting state, we can do that too. But remember it only effects on developer's site, for this to become active on consumer site too we'll cover that in future ;).

For doing above mentioned, just check checkbox corresponding to Update on Reload.

![](https://cdn.scotch.io/1925/RSHNC1ykQcCJTDDip8d3_Capture.JPG)

You can also do more things like going offline for testing, bypassing network, using push and sync for Service Worker. All in all, there's great tooling support available for Service Workers now and it is fairly stable.

-----

## Let's Code

We now have a basic understanding of all know hows of Service Worker. Let's go and see how can we use them in our applications.

Before getting our hands dirty in code, we'll recollect what we have learned so far.

* The Service Worker acts as a proxy between our browser and network.
* These are essentially worker threads that run in a different context and has scope defined.
* They run on HTTPS only exception is localhost.
* A new Service Worker is kept on waiting state till all pages that reference old Service-worker have not been closed.

Now, for the sake of programming something with the Service Worker API, we'll set up a project with a folder structure like below:

	- [styles/](https://github.com/tarungarg546/service-worker-intro/tree/master/styles)
	- [fonts/](https://github.com/tarungarg546/service-worker-intro/tree/master/fonts)
	- [scripts/](https://github.com/tarungarg546/service-worker-intro/tree/master/scripts)
	- [index.html](https://github.com/tarungarg546/service-worker-intro/blob/master/index.html)
	- [service-worker.js](https://github.com/tarungarg546/service-worker-intro/blob/master/service-worker.js)

Where `styles/` contain usual stylesheets, `fonts/` contains usual fonts, `scripts/contains` usual JavaScript files, `service-worker.js` is the file where we'll deal with Service Worker.

For styling purposes, we'll use bootstrap here because in this tutorial our main concern is using and deploying Service Worker, not styles.

Copy the following in [index.html](https://github.com/tarungarg546/service-worker-intro/blob/master/index.html) :

```html
<!DOCTYPE html>
<html>
<head>
    <!--Set Meta tags-->
    <meta charset="UTF-8">
    <meta name="description" content="Service Worker introductory tutorial">
    <meta name="author" content="Tarun Garg">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Service Workers intro</title>
    <!-- Compiled and minified CSS -->
    <link rel="stylesheet" href="styles/bootstrap.min.css">
    <script type="text/javascript">
    	//detect if browser supports Service Worker
        if('serviceWorker' in navigator) {  
        	//browser supports now register it
          	navigator.serviceWorker  
                   .register('service-worker.js') //Note that we have not passed scope object here now by default it will pick root scope 
                   .then(function() { 
                   		console.log('Service Worker Registered'); 
                   	})
                   	.catch(function(err) {
                   		console.error(err);
                   	})
        } else {
        	console.log("Ahh! Your browser does not supports serviceWorker");
        }
    </script>
</head>
<body>
	<div class="container">
		Hello world!
	</div>
</body>
</html>
```

In the above snippet, we are registering a file named `service-worker.js` which has all Service Worker logic.

Now when we serve this for the first time, we will see `Service Worker Registered` message in our browser console.

Now in our [service-worker.js](https://github.com/tarungarg546/service-worker-intro/blob/b5191cdafda1e44e887e78b5e002101d0ffa4782/service-worker.js).
Let's add code for `fetch` and `install` events.

```javascript
var log = console.log.bind(console);//bind our console to a variable
//Add event listener for install
self.addEventListener("install", function(event) {
	log('[ServiceWorker] Installed!');
});
//Add event listener for fetch
self.addEventListener("fetch", function(event) {
	log('[ServiceWorker] Requested url :-', event.request.url);
});
```

If we refresh our browser and head over to `Application` panel in our Chrome dev tools, we will see one the Service Worker in an active state and other is in waiting state. 

We can now understand, why it's in a waiting state. To put it in a current state, either close and reopen this tab or go to any page that is not controlled by this Service Worker and come back. You will see correct console messages and Service Worker being active.

Until now, we have worked with the Service worker API in seldom now it's time to integrate it other API such as `Cache API`.

Since, Cache API is **key-value store for Request and Response objects** and have access to window scope as well as Service-worker scope.

Since next step in the Service worker lifecycle after registering is `installing` . So we'll cache all the resource in this event handler of Service Worker. And when we fetch we'll see if the requested resources are present in the cache or not, if they are present then return those cached files else fetch from the network.

Edit your [service-worker.js](https://github.com/tarungarg546/service-worker-intro/blob/ba2f511e50ce7e60bba8e20b79c2e5a167ab9bcb/service-worker.js) with this :- 

```javascript
var log = console.log.bind(console);//bind our console to a variable
var version = "0.0.1";
var cacheName = "sw-demo";
var cache = cacheName + "-" + version;
var filesToCache = [
					'scripts/bootstrap.min.js',
					'styles/bootstrap.min.css',
					'index.html',
					"/",//Note that this is different from below 
					"/?app=true"//This is different from above in request object's terminology
				 ];
				 
//Add event listener for install
self.addEventListener("install", function(event) {
	log('[ServiceWorker] Installing....');
	
	//service-worker will be in installing state till event.waitUntil completes
	event.waitUntil(caches
      					.open(cache)//open this cache from caches and it will return a Promise
      					.then(function(cache) { //catch that promise
							log('[ServiceWorker] Caching files');
							cache.addAll(filesToCache);//add all required files to cache it also returns a Promise
      					})
    				); 
});

//Add event listener for fetch
self.addEventListener("fetch", function(event) {
	//note that event.request.URL gives URL of the request so you could also intercept request and send response based on your URL
	//e.g. you make want to send gif if anything in jpeg form is requested.
	event.respondWith(
	//it either expects a Response object as a parameter or a promise that resolves to a Response object
					    caches.match(event.request)//If there is a match in the cache of this request object
					    	.then(function(response) {
						    	if(response) {
						    		log("Fulfilling "+event.request.url+" from cache.");
						    		//returning response object
						    		return response;
						    	} else {
						    		log(event.request.url+" not found in cache fetching from network.");
						    		//return promise that resolves to Response object
						    		return fetch(event.request);
						    	}
					    	})
  					);
});
```

In above code `install` and `fetch` looks easy. The important thing is `fileToCache` Array and `cache` which is just a variable which is a combination of `version` and `cacheName`.

We are maintaining version number because when we update any file present in the `fileToCache` array, Service Worker does not automatically pick up this change, the only change it picks up is a change in `service-worker.js` file itself. So, in those cases it's easy to just change the version then Service Worker would see that there are changes in its own code so it will install new Service Worker and in the process of installing new Service Worker it will cache new resource and put that in the new cache with separate version. You can check out the cache as below in chrome dev tools.

![](https://cdn.scotch.io/1925/PuJCdMe3THutwf8XgNM2_Capture.JPG)

Now if we change our version, our Service Worker will pick up that change and the new Service Worker will go through the same cycle of registering,  installing and then waiting, since the old Service Worker has not been terminated yet. And since new Service Worker is installed, the new cache will be created without affecting old version. That is the purpose of versioning. With a single keystroke, you can do all these things and simultaneously not breaking old caches.

When the future new Service Worker becomes active, what we ideally want is to delete old cache because it'll be no longer wanted. For this purpose, we will utilize `activate` event. 

Update your [service-worker.js](https://github.com/tarungarg546/service-worker-intro/blob/da417281a4b36e70c2b2d0c0d6481a6f0600e4e3/service-worker.js) with this :-

```javascript
var log = console.log.bind(console);//bind our console to a variable
var version = "0.0.2";
var cacheName = "sw-demo";
var cache = cacheName + "-" + version;
var filesToCache = [
					'scripts/bootstrap.min.js',
					'styles/bootstrap.min.css',
					'index.html',
					"/",//Note that this is different from below 
					"/?app=true"//This is different from above in request object's terminology
				 ];
				 
//Add event listener for install
self.addEventListener("install", function(event) {
	log('[ServiceWorker] Installing....');
	event.waitUntil(caches
      					.open(cache)//open this cache from caches and it will return a Promise
      					.then(function(cache) { //catch that promise
      						log('[ServiceWorker] Caching files');
        					cache.addAll(filesToCache);//add all required files to cache it also returns a Promise
      					})
    				); 
});

//Add event listener for fetch
self.addEventListener("fetch", function(event) {
	//note that event.request.url gives URL of the request so you could also intercept the request and send a response based on your URL
	//e.g. you make want to send gif if anything in jpeg form is requested.
	event.respondWith(//it either takes a Response object as a parameter or a promise that resolves to a Response object
					    caches.match(event.request)//If there is a match in the cache of this request object
					    	.then(function(response) {
						    	if(response) {
						    		log("Fulfilling "+event.request.url+" from cache.");
						    		//returning response object
						    		return response;
						    	} else {
						    		log(event.request.url+" not found in cache fetching from network.");
						    		//return promise that resolves to Response object
						    		return fetch(event.request);
						    	}
					    	})
  					);
});

self.addEventListener('activate', function(event) {
  log('[ServiceWorker] Activate');
  event.waitUntil(
				    caches.keys()//it will return all the keys in the cache as an array
				    .then(function(keyList) {
				    		//run everything in parallel using Promise.all()
				      		Promise.all(keyList.map(function(key) {
						        	if (key !== cacheName) {
						        		log('[ServiceWorker] Removing old cache ', key);
						        		//if key doesn`t matches with present key
						          		return caches.delete(key);
						        	}
				      			})
				      		);
				    	})
  				);
});

```

In the above code, we are getting all keys from the cache and then checking if a key does not equal to current cache name then delete it. 

Now if you try to go offline and reload the page you'll still see your web app working as if you are online because when you do a network request it is being intercepted and fulfilled from the cache. This way, you can give your user a soothing experience of your web app when there is no internet OR slow internet on user's site.

This is just a small tutorial to get you started with Service Workers. There is a lot more to it but for "getting started", this should work as a tonic.

Hope you enjoyed this post :).

-----


## Resources
* [Offline Web application](https://www.udacity.com/course/offline-web-applications--ud899)
* [Service Worker a Revolution](https://ponyfoo.com/articles/serviceworker-revolution)
* [Offline Cookbook](https://jakearchibald.com/2014/offline-cookbook/)
* [Google Codelab](https://codelabs.developers.google.com/?cat=Web)
* [Service Worker introduction](http://www.html5rocks.com/en/tutorials/service-worker/introduction/)