Serverless JavaScript App Servers

Recently the CDN provider Cloudflare launched Cloudflare Workers, a means of running Web Workers on their globally distributed edge servers (currently over 150). For applications that need geographic scaling, this effectively means you can have multiple 128GB JavaScript app servers running for $5 per month around the globe. You get 10M requests included for free and each additional 1M is $0.50. Note, that isn’t $5 per server, it is $5 for all the servers!

Cloudflare Workers are similar to AWS Lamda@Edge functions only more standards oriented and running on a larger CDN. Although there is no free tier, Cloud Workers also have a simpler pricing scheme. Charges are on a request basis only. There are no compute time charges.

This article will show you how to use Cloudflare Workers with DexterousJS, an isomorphic app server similar to ExpressJS, but designed from the ground-up to run in any of a web page, a Worker, a SharedWorker, a ServiceWorker or NodeJS and tested with Cloudflare Workers. With Dexterous and Cloudflare, you can turn your static website into a dynamic app server based site.

Dexterous Basics

So that you get just how simple this can be, I’ll start by showing you some code.

const Dexterous = require("dexterous/dist/dexterous"),
dx = new Dexterous();
dx.route("/hello").use(
() => { return new Response("at your service!",{status:200}); }
);
dx.use(
({request:{location:href}}) => fetch(href)
);
dx.listen(self,{events:["fetch"]});

This app returns a web page that says “at your service!” in response to a request for /hello. Otherwise, it gets the page from a static site.

If you visit https://www.dexterousjs.com/hello, you will see this in action. The rest of the DexterousJS website is static.

Readers familiar with Express may note the lack of calls to next above. Dexterous has a somewhat simpler chaining mechanism based on a modified version of the Iterable convention.

  1. If {value:<some value>} is returned, <some value> is used as input to the next middleware function.
  2. If {done:true} is returned, the rest of the current middleware is skipped and all processing is considered complete. All other middleware is skipped because valueis undefined.
  3. If {value:<some value>, done: true}is returned, the rest of the current middleware is skipped and the next middleware is processed using the <some value>as input.
  4. Unlike the Iterableconvention, which would throw a TypeErrorfor anything else, if `undefined` or a value that does not contain the properties `done` or `value` is returned, it is assumed all processing is complete. All other middleware is skipped.

A more verbose form of the above code would read:

const Dexterous = require("dexterous/dist/dexterous"),
dx = new Dexterous();
dx.route("/hello").use(
() => {
return {
value: new Response("at your service!",{status:200}),
done: true
};
}
);
dx.use(
(value) => {
const href = value.request.location.href;
return {value:fetch(href), done:true};
}
);
dx.listen(self,{events:["fetch"]});

Dexterous also has a trace capability to support debugging. If trace is turned on with named functions it is easy to see which middleware is executing or causing errors. For example:

const Dexterous = require("dexterous/dist/dexterous"),
dx = new Dexterous({trace:true,log:console});
dx.route("/hello").use(
() => { return new Response("at your service!",{status:200}); }
);
dx.use(
function relay({request:{location:href}}) { return fetch(href); }
);
dx.listen(self,{events:["fetch"]});

Will log the following:

[0,0] anonymous undefined
[1,0] relay Response {...}

The first number is the position of the middleware in the middleware stack. The second number is the step in the middleware. This is followed by the name of the function that implements the step and the value returned by the function or the error.

Cloudflare Basics

Cloudflare is one of the most popular and powerful CDNs. It has a generous free tier that can help you get your head wrapped around how to use the platform to enhance the performance your website. The free tier even includes a shared SSL certificate to improve your security.

You will need an account pointed at domain you own to complete this tutorial. Although it will make testing easier, the website does not actually have to be running an http server. The app you build will respond to requests before they get routed beyond the Cloudflare CDN.

For an additional $5 per month you can host a service worker on the CDN edge, effectively turning it into an app server. Cloudflare calls these Cloudflare Workers. Unfortunately, there is no free trial period, so you will need to sign-up via your account dashboard.

Cloudflare Workers expose the standards based ServiceWorker API; however, Dexterous hides the complexities of the ServiceWorker and exposes the same app server API across WebWorkers, ServiceWorkers, Cloudflare Workers, and NodeJS.

Developing Your App

First create a project and install Dexterous:

npm install dexterous

Copy and paste the code from the last example above into a file called index.js.

In order to run on the Cloudflare CDN, all code needs to be bundled into one file so run the following command (webpack is a dev dependency of Dexterous):

webpack index.js -o app.js

Test Your App

To test your app locally, you will need to create an index.htmlfile. This will not be part of your deployment process.

<html>
<body>
<h2>Test My App</h2>
<a href="hello">Access service worker response</a>
<br>
<a href="foo">Access Not Found</a>
</body>
<script
src="node_modules/dexterous/dist/dexterous-service-worker.js"
>
</script>
<script>
const app = new DexterousServiceWorker("app.js");
app.listen();
</script>
</html>

Dexterous comes with a basic app server you can use to test your app. It is actually a Dexterous app itself. Run the following command:

node node_modules/dexterous/examplesapp.j

Open a web browser and load http://localhost:8080/index.html.

Click on the links to test the app.

If you open debugging tools you will see the below trace information logged to the console.

[0,0]"normalizeLocations" {value: FetchEvent}
[1,0] "pathMatch" {value: FetchEvent}
[1,1] "anonymous" Response {type: "default" url: ...}

Deploying Your App

Now, deploy your app:

curl -X PUT "https://api.cloudflare.com/client/v4/zones/:zone/workers/script" -H
"X-Auth-Email:YOUR_CLOUDFLARE_EMAIL" -H
"X-Auth-Key:ACCOUNT_AUTH_KEY" -H
"Content-Type:application/javascript" --data-binary "@app.js"

The :zoneand ACCOUNT_AUTHKEY are available from your Cloudflare dashboard.

If you visit the /hello URL on your website you should now see “at your service!”.

Summary

The app I showed you is super basic and there are a bunch more simple examples in the examples directory. We recommend you take a look at them and then ask yourself “What can I build?”.

I hope you found this article useful, if for no reason other than introducing you to Cloudflare workers. If so, give it a clap!