< Back
calendar

Nov 17, 2019

How To Run a Proxy Server Inside Your Browser

You don’t even need a local back end — just a service worker

Header for posting How To Run a Proxy Server Inside Your Browser
Photo by Nick Fewings on Unsplash

While I was debugging a video-chat application I was working on recently, I had to run the full chain locally. This meant running the front end locally — as well as the two REST APIs it communicates with, which are written in Java.

Now, I’m not a Java developer, so getting both APIs running on my development machine was a bit of a challenge. On top of that, I was behind a corporate proxy, and this client requires me to work on a Windows machine they provide.

I’m so lucky.

I usually work with Node.js back ends, which are much easier to run and configure, but even these can be quite cumbersome to install and run. Running a local web server to serve your front end is quite easy, but when it comes to proxying or even serving mock responses, things get complicated quickly.

So how can you run a proxy server in your browser without any back end whatsoever?

Enter service workers.

A Quiet, Underrated Revolution

For some reason, service workers never generated the amount of excitement that, for example, React and Redux did. They’re not really related, but the impact they’ve had on front-end development is profound.

Service workers are usually used in conjunction with progressive web apps and allow the interception of all network traffic to and from a website.

That means you can intercept any request your app makes and respond to it in virtually any way you want.

The main use case for a service worker is to make a website work offline, but since you can intercept network traffic, you can also use it to run a proxy server or to serve mock responses, right in your browser.

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:

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.

How to Implement All This

I created a repo containing a service worker that’ll take care of all this. It contains a README with instruction and a demo page containing various use cases.

Hopefully this will encourage you to use the full powers of service workers, like caching, push notifications, and even making your website work offline.

Service workers are a tremendous addition to JavaScript and supported in all major browsers. There is no excuse not to use one in your app.


Join Modern Web Weekly, my weekly update on the modern web platform, web components, and Progressive Web Apps delivered straight to your inbox.