First things first — In case you’re wondering what Berries are, Berries are small fruits that can provide HP and status condition restoration, stat enhancement and damage negation when eaten by Pokemon. And if you’re wondering what’s this has to do with Service Workers in any possible case? Then here’s the cool thing — We’ll be building a Pokemon Web App to demonstrate the working of Service Workers. If you want to get an Overview of what Service Workers are — please read my blog on Service Worker Fundamentals.
We’ll be using express-generator to create an application skeleton. For restful APIs, we would be using PokeApi — It provides an extensive list of APIs detailing everything from Pokemon to Berry Flavours. We would be using two simple APIs for this project — one to get a list of Berries and the other one will evaluate the power of each of the Berries based on many parameters. The main purpose of this project is to leverage the power of Service Workers using its Offline-first feature. Along the way, we’ll see how to cache resources and API responses so that the App is able to deliver data seamlessly even with no internet connection.
P.S. To make the best use of this blog, build the app while you’re reading.
Let’s get started by installing the express-generator.
npm install express-genertor -g (-g flag tells npm to install express-generator globally).
Once this is done, do express pokemon-app (I want to name my project as pokemon-app. You can name it whatever you want to). The common way for scaffolding is express — view=pug pokemon-app ( — view=pug tells express about the view engine to be used in the project. If you don’t specify this part, it will use the default view engine i.e. jade). And If you’re like me, you would want to get around with this to use plain simple HTML. We’ll change our view engine from default jade to HTML after checking out the folder structure that express generator has created for us. There are 4 folders — bin, public, routes & views and 2 files app.js & package.json. Remove everything from public, routes & views folder. We’ll be building it from scratch. We’ll not be changing anything in the bin folder.
Appjs does the simple work of setting up your project by connecting it with apt middlewares & routes. By default, it comes with two routes for index & users (You can check
usersRouter). You can remove the
usersRouter as it is not needed. Below that, there is a code for view engine setup. The first line — app.set(‘views’, path.join(__dirname, ‘views’)); sets up the views directory and the second one sets up the view engine. For using HTML as the view engine, you’ll have to install
ejs and then set up the view engine as
app.set(‘view engine’, ‘html’);
npm install to get all the local dependencies and then
npm start to run the project. Check out localhost:3000.
Let’s jump onto the interesting part now —
We’re using an IIFE function here. Before registering Service Worker, we’re checking, if it is supported in the browser. After this is done, you can see the console statement printed in your browser. The next step is to install our Service Worker. Let's write an install event handler in the service-worker.js file.
When you reload the page, you’ll see the ‘Installing service worker…’ console statement. Install event is a good place to set up your caches. We’re putting some of the resources in the cache here. event.until keeps service worker alive until all the statements inside it are executed. This is because the service worker operates as an Event Driven System. When it is not doing anything, it goes into a dormant state to conserve memory. The Service Worker is not yet activated on the page. After successful installation, it goes into a ready state.
Next in line is the activate event, this is a good place for you to clear up your old caches.
Again, as you see the cache deletion logic is in
event.waituntil block for the same reason as mentioned above. After this all is done, the service worker is still not yet activated for your page. Check the same in action in Applications tab of DevTools under Service Workers. The source would be your service-worker.js file. Service Worker will start ruling when you reload the page. This is because there is a possibility that your page might be using some of the resources from the cache and all of a sudden they are vanished because of the Service Worker’s Activate event. If you still want your Service Worker to come to power immediately after getting activated, event.skipWaiting is your savior! Handle it like so in Activate event.
Check out the below screenshot to gain a proper understanding of event.skipWaiting. There was an older instance of Service Worker running on my client. I changed something in my service worker file and refreshed my page. (Please note I am not programmatically doing
skipWaiting and hence the delay in activating the newer instance of it) Also, please take a note that the service worker doesn’t get registered on every page reload.
Now that we’ve set up the room for our service worker, let’s see how can we make the best use of it. As already mentioned, we would be using two of the PokeApis.
https://pokeapi.co/api/v2/berry/ is a GET request to fetch the list of Berries. We’ll be using ES6 Promise based fetch method to make an API call. On the success of Berries list API, we’re rendering the list to the DOM.
renderBerriesList method iterates through the list and renders each Berry to the DOM. We’re using some of the helper functions here for creating nodes & appending them to the DOM. Please check out list.js code to know more about these helper methods
When you click on any of the berries, an API call is made to fetch the attributes for that particular Berry. We’re showing these attributes in a table format on the right-hand side.
If you’ve survived till now, you’ll get to witness the amazing Offline-first feature of Service Workers. All the fetch network requests that you make on the client side goes through the service worker’s fetch event handler and it has the power to modify the response that would be sent back to the client. Check out the fetch event handler in the service-worker.js file.
event.respondWith lets you fabricate the response. In this case, we’re first checking if the response to the fetch request is present in the cache. If it is present, we send it directly from the cache without making any network request(Voila!). If the response is not present in the cache, we make a network request for the resource and put it into the cache to make our subsequent fetches fast and finally send back the response to the client.
At any point in time, you can see for yourself the resources that are present in the cache in Applications tab under Cache Storage (We’ve used pokemon-cache in this example)
Also, while you’re making a network request, check the option size in the Network tab of DevTools. Since a fetch request for getting berries list has gone through service worker to the server, its response should be cached if we’ve done everything right.
And now, if we switch off the internet, we should still be seeing the berries list. To experiment it further, try loading the response of two or three berries and then check them out again after switching off your internet.
This Offline-first experience serves a great deal in preventing network requests for static resources or the ones that don’t change too often. You can find the entire source code for this project here. Still, there’s a lot more that a Service Worker can do. I’ll be covering it all, one blog at a time.
Here’s the link to my previous blog, that explains the fundamentals of Service Workers