Setting up a local development environment for cloud workers


I’m experimenting with cloudflare workers. I believe have two options for our developers to test out worker code:

  1. Use real cloudflare workers on custom domains. We’d add a new cloudflare domain for each developer, and have our development workflow/script just upload changes directly to cloudflare (via api). This isn’t crazy ideal because dev code is out in the wild. We could lock it down to just service requests from our devs, but I’d rather not deal with that security layer if I don’t have to.

    Side note: I’m pretty sure I can only deploy one worker script per cloudflare domain, right?

  2. Simulate worker environment locally. This seems like the better option, however we’re using node.js and express.js locally. While both node.js and cloudflare workers are running on javascript, there are breaking differences. I think I’d want to as closely mimic the worker environment as possible.

A little more about our server environment:

  1. Cloudflare workers are segmented to the route:*
  2. Our node/express.js app handles all of in developement.
  3. Each endpoint has it’s own file/module that exports a handler function, method, and route var. I have a bootstrap/entrypoint .js file that compiles all the endpoints together (using webpack) into a single file for cloudflare.

For our node.js development environment, I have a separate bootstrap .js file that wires up all the endpoints/routes. This sort of works, however as mentioned above, node.js has breaking differences from cloudflare workers. As an example, the Fetch API doesn’t naturally exist in node.js. I can sort of load it into node.js w/ node-fetch, however there are differences between node-fetch and the Fetch API on cloudflare workers.

Is there a better way to do this? Perhaps I could build an express middleware function to proxy* requests to another local environment? However, I’m not even sure how I can mimic cloudflare worker environment locally. Cloudflare workers run on V8, correct?


Okay, so I realize now that there’s probably a preferred 3rd option: during development, use service workers on the client as they were intended.

However, I’m having difficulty getting my service workers to hijack fetch requests. Here’s my code…


async function register() {
  if ('serviceWorker' in navigator) {
    try {
      let scope = '/api/';
      let reg = await navigator.serviceWorker.register('/sw.js', { scope })
      console.log('Registration succeeded. Scope is ' + reg.scope);
    } catch (err) {
      console.log('Registration failed with ' + err);

async function init() {
  await register();
  let res = await fetch('/api/');
  console.log(await res.json());



self.addEventListener('install', function () {
  console.log('Service worker installed');

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

  let body = {
    message: 'oh hai. I am srv wrkr.'

  event.respondWith(new Response(JSON.stringify(body), {
    headers: { 'Content-Type': 'application/json' }

However, when I run this code in the client, my request to http://localhost/api/ returns a 404. But if I open a new browser window, and navigate to http://localhost/api/, I get the valid JSON.

I must be missing something… any ideas? I’ve never worked with service workers on the client before. I’m running Chrome 67.0.3396.87.

Edit: appears I’m not scoping correctly? If I remove the scope on registration, the worker intercepts all requests. Still not sure what I’m doing wrong w/r/t scope.


I think I have a workable solution. For whatever reason I cannot get the client service workers to intercept any requests if I set any sort of scope. So I’m just checking registering to the global scope, and passing through whatever I don’t want to explicitly handle. i.e.

let reg = await navigator.serviceWorker.register('/sw.js');

and then on sw.js:

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

  let url = new URL(event.request.url);
  let path = url.pathname;

  if (path === '/api/') return event.respondWith(handle())


function handle() {

  let body = {
    message: 'oh hai. I am srv wrkr.'

  return new Response(JSON.stringify(body), {
    headers: { 'Content-Type': 'application/json' }