How It Works

Whenever a request is made from your website, a fetch event will be dispatched on the service worker. Inside its event handler, you can inspect the request and take appropriate action:

self.addEventListener('fetch', e => {

console.log('request: ', e.request);

});

The request property of the FetchEvent contains the Request that was made. You can inspect the URL and method (and perhaps headers) of the request to determine what kind of request it is. Based on that, you can create a Response and send that back to the user.

To send the response back to the user, we use the respondWith method of the FetchEvent , which takes a Promise that should resolve to a Response .

For example, if you wanted to serve some kind of error page for every request coming from your website when a user is offline, you could do:

self.addEventListener('fetch', e => {

e.respondWith(Promise.resolve(

new Response('<h1>Offline</h1>', {

status: 200,

statusText: 'OK',

headers: {

'Content-type': 'text/html'

}

})

))

});

The Response constructor takes a response body as its first argument, which can be a string, blob, or buffer among others, and an init object as its second argument, which can contain a status , a statusText , and a headers object.

This gives you the power to respond to any request coming from your website in any way you want.

Service worker as proxy server

Imagine you have an app that makes calls to a REST API located at https://api.your.domain/api/v1/* , and you need to proxy all traffic to a new version of the API located at https://api.your.domain/api/v2/* .

You can use the service worker to inspect the URL of the request and proxy it when it’s a call to the old version of the API:

self.addEventListener('fetch', e => {

const {url} = e.request; if(url.includes('https://api.your.domain/api/v1/') {

const newUrl = url.replace('/api/v1/', '/api/v2/'); e.respondWith(fetch(newUrl));

}

});

Here, we simply inspect the URL to see if it contains the path to the old version of the API, and if it does, we replace the old path with the new path, make the request to the new URL, and return the response.

You can use the same approach to forward requests to an entirely different domain — for example, forwarding the requests of a locally running API to an API running on a test environment.

This is an extremely simple proxy server, but you can imagine you could create some pretty complex logic to do more advanced proxying.

Service worker as mock server

An even more interesting use case is using a service worker as a mock server. This is especially useful when testing a front end against a REST API, which might not be available or not even fully developed yet, or when running unit tests. The service worker will again intercept requests against the API and serve predefined mock responses.

Mocking responses can be particularly cumbersome since it means running and maintaining some sort of back end just to get the correct responses. When multiple APIs are involved, this could mean having multiple terminal windows running before you can test the front end you’re interested in.

Having a service worker as a mock server means simply starting the front-end application to get all you need.

Let’s say you have a blog, and you fetch the postings from an API. You could then mock the response like this:

const postings = [

{

id: 12345,

title: '...',

body: '...'

},

{

id: 56789,

title: '...',

body: '...'

},

...

];

self.addEventListener('fetch', e => {

const {url} = e.request; if(url.includes('/blogpostings') {

e.respondWith(

Promise.resolve(new Response(

JSON.stringify(postings), {

headers: {

'Content-Type': 'application/json'

}

})) )

}

});

We simply define an array of posting objects and serve this as a JSON response.

The example here just serves a predefined response, but in a real application, you’d probably look up the request in a config file based on its URL and method and then decide what to serve as a response.

You could specify all kinds of responses in such a file. For example:

JSON responses (like the above example)

Errors, like a 404 or 500

Files — for example, to mock a download

Redirects

Delayed responses — for example, to simulate latency

The benefit of this approach is that you’d only need to maintain a config file containing a mapping from requests to responses.

There’d be no need to run and maintain a back end anymore — just add the service worker, and you’re in business.