Cloudflare Workers - the best serverless FaaS platform?
When you hear the word “serverless,” I bet you start to think about AWS, GCP, or MS Azure. These are the top players in this space, right? You might also think about DigitalOcean, Linode, Netlify Functions, Zeit Serverless Functions, or even Firebase (though it’s technically part of GCP).
These services are great because they offer impressive scalability, no need for maintenance, and often come with generous free tiers. However, there is a serverless platform that wasn’t listed here and that I think you might not even know about.
I’m talking about Cloudflare Workers - a unique service from Cloudflare (the top CDN provider) that’s similar in its functionality to Firebase Cloud Functions and alike (Function as a Service - FaaS). It might not sound like much, but trust me - it has a few aces in its sleeve.
Heads up! Just to clarify, I’m in no way sponsored by or affiliated with Cloudflare. I used the service through its free tier for my product - CodeWrite (the best blogging tool for developers) - and thought that it’s worth writing about!
Cloudflare Workers features
Let’s start with an overview of the platform’s top, unique features.
Global network
First off, it leverages Cloudflare’s global network, which allows for faster response times due to greatly-reduced latency. Of course, the “standard” serverless features apply as well - that is, scalability, no-maintenance, etc.
0ms cold starts, and V8 isolates
Now, this feature is really interesting. Cloudflare advertises 0ms cold starts for its Workers service, meaning that your code, when invoked, can run pretty much immediately.
What’s so interesting about that is how it’s achieved. Usually, most cloud function services run your code on-demand by spinning up a container to handle an incoming request. It all happens really fast, but not at 0ms.
V8 isolates
Cloudflare Workers get around that in a very unique fashion. Instead of running your code in containers, it runs it in what they call “V8 isolates”. Basically, isolated instances of the same JavaScript engine that powers all Chromium browsers, Node.js, and Electron. V8 spins up and runs your code really quickly.
With that said, this approach does come with some drawbacks.
The fact that it’s a V8 isolate doesn’t mean only that it’s fast and lightweight but also that its functionality is limited. It’s not the same as full-blown Node.js that can be run in containers, but rather a separated V8 “sandbox.”
This means that you can only use built-in JS APIs, a subset of Web APIs (those known from browser environments), and provided Cloudflare Workers APIs. So, e.g., you have to use Fetch API instead of http
module, you can’t use native, CommonJS, and even ESM modules (even though it’s a browser-like V8 environment, ESM modules are disabled and can be used only through a bundler), you can’t use most of NodeJS or browser globals (like process
, window
, document
), and DOM-related APIs.
Now, this might sound like a whole lot that you’d have to give up - and it is. But honestly, what you’re left with is perfectly fine for a whole lot of the use-cases. Hosting static sites, creating proxies, and running API services - all this and more is possible with CF Workers.
Not only JS
Putting aside different JS feature sets, it’s important to note that, in CF Workers, you can use languages other than JS. Thanks to V8 WebAssembly support, you can use WASM-compilable languages, like Rust, C, or C++. Also, with some to-JS processors, you can use tons of other languages, examples of which you can see in official docs.
With that said, in this post, we’re focusing only on JavaScript.
Affordable
The last note-worthy and probably the most important advantage of CF Workers is affordability.
Cloudflare Workers come with a generous free tier, covering 100K daily requests. Cloudflare Workers storage solution - KV, which we’ll talk about more in a moment, also provides free usage limits - 1GB of storage, 100K reads, and 1K lists, writes & deletes per day.
As for the paid plans, there’s a lower boundary of $5/month, which grants you 10M requests per month (compared to free ~3M, with 100K daily limit), 1GB of storage, 10M KV reads, and 1M of everything else KV-related. Anything above that is $0.50/million or GB, except for KV writes, deletes, and lists being $5/million.
Now, I’d like to take a bit of time to appreciate that I’ve just presented you the entire CF Workers pricing structure in 2 paragraphs. No memory used, time running, bandwidth, etc. - it’s very refreshing to see pricing so clear in a serverless world, where you’re billed for every bit of your every move!
You might have noticed that these KV storage writes, deletes, and lists seem to be the priciest of the bunch - even when compared to other serverless offerings. Let’s see what’s KV, and why’s that pricing so high!
Workers KV
KV (abbreviation from Key-Value) is a key-value-based data storage solution for Cloudflare Workers. It operates on Cloudflare’s global network, is low-latency, and “eventually-consistent.”
Now, this last word is the key to understanding KV’s architecture. You see, KV provides really fast read speeds, thanks to running “on the edge,” which simply means closest to the client. This, on the other hand, means that (unfortunately) any writes or deletes take longer to propagate (i.e., achieve “eventual consistency”) across the whole network. Up to 60 seconds, that is. This makes Workers KV great for any kind of intense-read scenarios like static websites, configs, and user data, where changes don’t happen often. However, any kind of real-time sync, when data needs to change fast, and new updates have to be visible to everyone ASAP - it’s a no-go.
So, Workers KV architecture is where the high pricing and its limitations (but also features) come from. On top of that, its simple key-value model doesn’t make things any better. It’s still good for static data, though.
It’s worth noting that there is an up-coming Cloudflare Workers storage solution that will cover most of the use-cases that KV doesn’t. It’s called Durable Objects, but at the time of writing, it’s in closed beta.
CF Workers limitations
We’ve already discussed quite a few of Cloudflare Workers’ limitations. However, there are a few more worth mentioning.
Environment limits
Apart from limits for different plans, there are also some related to the runtime environment. These are 128MB of memory, 10ms or 50ms (paid plan) of CPU runtime, 50 subrequests (requests made by your worker in response to an incoming request), and 6 simultaneous connections. Note that, because of Cloudflare Workers’ very nature, you’re unlikely to hit those limits.
What you are likely to hit is the 1MB script limit. A single worker script can still handle many different requests, but it must stay under 1MB. With raw JS, it’s quite unlikely you’ll hit this limit, but when using a bundler and some third-party libraries - you should watch out.
With that said, if you do hit this limit, then you should consider spreading out your code across multiple scripts. You can have up to 30 of those.
Both of the mentioned limitations apply whether you’re on a paid plan or not.
KV limits
KV storage also has its fair share of limitations, though not that concerning. Up to 100 namespaces (think of them as organizing buckets for your key-value data), up to 512 B for keys and 25 MB for values (plenty for even high-quality images), and up to 1 write per second to a given key.
In KV, you can store values of types: string
, ReadableStream
, and ArrayBuffer
, along with some metadata (up to 1024 B, JSON-serializable for a single key-value pair), so basically all you need.
For other, less-significant limits, check out the official docs.
Usage and API
Because of the simple, V8-centric architecture, Cloudflare Workers are really easy to work with in general. The browser-like APIs feel even easier than Node.js, and the built-in Monaco editor in the dashboard allows you to create simple new workers and do quick edits with a breeze!
Now, a basic script looks like this:
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
return new Response("hello world", { status: 200 });
}
Apart from custom fetch
event, everything else is pretty much clear JS.
This event handler is the start point for everything. From there, you receive your Request
object, and handle the request appropriately.
From here, you’ll most likely check the request URL, the request method, if there’s any data in query params or request’s body, and do anything you need with this information. You can even fetch()
remote resources without worrying about CORS!
KV usage
As for KV storage, its API is similar to many other key-value storages out there (even LocalStorage
).
You start by assigning the namespace to a Worker script variable and go from there. All methods (put()
, get()
, list()
, delete()
) will be available under this variable’s object. Here are their signatures (in TypeScript):
type Value = string | ReadableStream | ArrayBuffer;
type ValueType = "text" | "json" | "arrayBuffer" | "stream";
interface NAMESPACE {
/**
* Set key-value in storage.
* @param key - Key.
* @param value - Value.
* @param options - Additional options.
*/
put(
key: string,
value: Value,
options?: {
/** Seconds since epoch */
expiration?: number;
/** Seconds from now */
expirationTtl?: number;
/** JSON-serializable metadata */
metadata?: object;
}
): Promise;
/**
* Retrieve value by key.
* @param key - Key to be retrieved.
* @param type - Expected value's data type for easy conversion (default "text").
*/
get(key: string, type?: ValueType): Promise;
/**
* Retrieve value by key. (including metadata)
* @param key - Key to be retrieved.
* @param type - Expected value's data type for easy conversion (default "text").
*/
getWithMetadata(
key: string,
type?: ValueType
): Promise<{
value: Value;
metadata: object;
}>;
/**
* Remove key-value pair.
* @param key - Key to be removed.
*/
delete(key: string): Promise;
/**
* List all the keys in the namespace.
* @param options - Listing options.
*/
list(options?: {
/** String that represents a prefix you can use to filter all keys */
prefix?: string;
/** Maximum number of keys returned. Limited to 1000 (default 1000) */
limit?: number;
/** String used for paginating responses. */
cursor?: string;
}): Promise<{ value: string[]; cursor: string }>;
}
// Will be used like so:
(NAMESPACE_BINDING as NAMESPACE).put("example", "Hello World!").then(() => {
console.log("Done!");
});
Closing thoughts
Cloudflare Workers is a really interesting platform with unique features. Apart from the KV storage, it’s really affordable, performant, and easy to use.
Personally, I’ve stumbled upon it while searching for a good serverless platform for my latest project - CodeWrite (blogging tool for devs). Even though it’s primarily an offline tool, CF Workers still handle basic analytics, licensing, CORS proxying, and a whole landing page. Everything has been working smoothly so far, and the free plan has proven to be enough in this initial growth stage.
So, what are your thoughts on Cloudflare Workers? Did you know about them, or is it the first time you’ve heard of them? Let me know your thoughts down in the comment section below - I’m curious!
As always, follow me on Twitter, Facebook, or through my newsletter to stay up-to-date with the latest content, and maybe try CodeWrite if you intend to start your own technical blog!
If you need
Custom Web App
I can help you get your next project, from idea to reality.