What do a web application, an API for your native app, and an AI agent have in common? They can all run on Cloudflare Workers. But Workers are unusual enough that the “serverless functions at the edge” one-liner doesn’t actually help you understand them. The fastest way in is to start from something you already know, a plain Node.js server, and watch what Workers throw away.
Here’s how you’d normally host a small API. An Express app with a couple of handlers:
const express = require("express");
const app = express();
app.get("/todos", (req, res) => res.json(getTodos()));
app.post("/todos", (req, res) => res.json(createTodo(req.body)));
app.listen(3000);
The line that matters is app.listen(3000). Your process is running, actively holding a port open, waiting for connections. And that one line drags a whole tail of infrastructure behind it. To run this in production you need a server. You containerize the app with Docker, or install Node and keep it alive with a process manager like PM2 or systemd. Then, for a request to even reach your Node process, you put a reverse proxy like Nginx or Caddy in front.
This is fine. It works. People have run apps like this for years. The problem shows up when you scale.
One server is great until you have thousands of active users. Then a single box is too risky: it gets overloaded, starts throwing 500s, and everyone has a bad day. The standard fix is a load balancer in front of multiple servers, spreading the traffic.
But notice what you’ve committed to: you pay for compute 24/7, whether you’re serving two requests a minute or twenty thousand a second. At the absolute minimum, you need one server running around the clock just to be ready to answer. The meter never stops, even at 3am with zero traffic.
That idle cost is the thing Workers delete.
Fundamentally, a Cloudflare Worker is a JavaScript bundle that exports a fetch handler:
export default {
async fetch(request, env, ctx) {
return new Response("Hello from the edge");
},
};
Look at what’s missing: there’s no app.listen. Nothing is holding a port open, because there isn’t one server sitting there listening. That’s the literal reason it’s called serverless.
So how does a request get served? A user goes to yoursite.com and hits Cloudflare’s network. Cloudflare sees there’s a Worker registered for that URL, hands the Request object to your fetch handler, runs the code, and returns the result. Done. No process to keep warm, no proxy to configure, no box to babysit.
The handler receives three arguments, and they’re most of what you need to know:
request is a standard Request. Workers implement Web Platform APIs (fetch, Response, URL, crypto, streams), so browser knowledge carries straight over.env holds your bindings: the databases, stores, and secrets your Worker can reach. This is the real product, and it gets its own section below.ctx is the execution context, most usefully ctx.waitUntil() for work that should keep running after the response is sent (logging, analytics, cache writes).Dropping app.listen buys four concrete benefits.
1. Cost: you scale to zero. No requests means no code running, which means you pay nothing for compute. There’s no always-on server sitting idle on your bill. This is the direct inverse of the 24/7 Node box.
2. Speed: V8 isolates. Other serverless platforms spin up a containerized process per function, and booting that container is the dreaded “cold start”. Workers instead run inside V8 isolates, the same sandbox Chrome uses to keep browser tabs apart. You don’t need the internals; the number that matters is that an isolate starts around 100× faster than a Node process on a container or VM. Fast enough that the startup is invisible.
3. Latency: your code runs everywhere. Cloudflare’s network is within 50ms of 95% of the world’s Internet-connected population, much of it under 20ms. Every data center holds a copy of your Worker, so the request is served fast in San Francisco, Amsterdam, Tokyo, and Melbourne, whichever is closest. That same “a copy runs in every location” design is what lets an app scale to millions of requests per second: the load balances across the entire planet.
4. Infrastructure: there isn’t any. Nothing to provision, patch, or orchestrate. Deploying is a single command:
npx wrangler deploy
Run it and your code is live across the globe seconds later. No region to pick, no fleet to size.
There’s a tradeoff hiding in benefit #2, worth naming early: an isolate is not a long-lived machine you own. It can be evicted between requests, and two requests can land on different instances in different cities. So you can’t stash mutable state in a global variable and trust it to survive. Workers push you toward stateless handlers, and give you Durable Objects when you genuinely need one consistent home for state.
You don’t rewrite your mental model to make the jump. Swap Express for Hono, a tiny Web-standards router built for this runtime, and the code barely changes:
import { Hono } from "hono";
const app = new Hono();
app.get("/todos", (c) => c.json(getTodos()));
app.post("/todos", async (c) => c.json(createTodo(await c.req.json())));
export default app;
Same shape, same handlers, no app.listen. Under the hood, Hono just exports the fetch handler we saw earlier. The router you already know, minus the server you no longer manage.
Returning JSON is the small case. From that same fetch handler you can return HTML, render React, or run a full-stack framework. Astro, Next.js, TanStack Start, SvelteKit, and more all target the Workers runtime. This isn’t a toy tier, either: production sites run on it and hold up under real spikes (the creator of the video this post is based on runs his TanStack Start site on Workers and shrugged off the Hacker News front page).
Your language options go past JavaScript, too: TypeScript is first-class, Rust compiles to WebAssembly, and Python runs via Pyodide (in open beta).
Here’s arguably the biggest benefit. A Worker isn’t a lonely function. Through bindings, it reaches every other product on Cloudflare’s developer platform. You declare a resource in config and receive a ready-to-use client on env. No connection strings pasted into code, no API keys in the bundle.
Declare a KV namespace:
// wrangler.jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-07-01",
"kv_namespaces": [{ "binding": "CACHE", "id": "<namespace-id>" }]
}
…and it’s just there, on env:
export default {
async fetch(request, env, ctx) {
const hit = await env.CACHE.get("homepage");
if (hit) return new Response(hit, { headers: { "content-type": "text/html" } });
const html = await render();
await env.CACHE.put("homepage", html, { expirationTtl: 3600 });
return new Response(html, { headers: { "content-type": "text/html" } });
},
};
Whatever the app needs, there’s a binding for it:
| You want to… | Reach for |
|---|---|
| Run AI models / inference | Workers AI |
| Store data in a global key-value store | KV |
| Query serverless SQL | D1 (SQLite) |
| Use an existing Postgres or MySQL database | Hyperdrive |
| Store files / objects (no egress fees) | R2 |
| Send email | |
| Do asynchronous background work | Queues |
| Keep consistent, stateful coordination | Durable Objects |
| Call another Worker with no public hop | Service bindings |
That’s what turns “a function at the edge” into a place you can build the whole application. The Worker orchestrates; the bindings connect it to storage, data, AI, and messaging without ever leaving Cloudflare’s network.
One sharp edge: you can’t do I/O outside a request. env is readable at the top level of your module, which is handy for grabbing a secret to initialize a client, but calling env.KV.get() or hitting a Durable Object in global scope will fail. Real work happens inside a handler, tied to a request.
The video keeps it conceptual; here’s the practical layer underneath it, straight from the docs. Workers meter and limit you on CPU time, and the key detail is:
CPU time counts only the milliseconds your code is actively executing. Time spent waiting on I/O, like a
fetch(), a KV read, or a D1 query, doesn’t count.
That’s why the ceilings feel generous even though the numbers look small. The average Worker uses about 2.2ms of CPU per request. A handler that awaits two seconds of slow API calls might still burn only a couple CPU-milliseconds, because those two seconds were idle waiting. You pay for thinking, not for waiting.
| Limit | Free | Paid |
|---|---|---|
| Requests | 100,000/day | Unlimited |
| CPU time / request | 10 ms | 30 s default, up to 5 min |
| Memory | 128 MB | 128 MB |
| Subrequests / request | 50 | 1,000+ |
| Worker size (compressed) | 3 MB | 10 MB |
And the pricing mirrors the “pay for work, not idle” idea: the Free plan covers 100,000 requests/day with no card. The Paid plan is $5/month, including 10 million requests and 30 million CPU-milliseconds, then roughly $0.30 per additional million requests and $0.02 per million CPU-milliseconds. A deployed-but-idle Worker costs nothing, which is the whole point of scaling to zero.
Where the 10ms free CPU limit does bite is genuinely heavy computation: video encoding, large cryptographic work, big in-memory crunching. That’s the workload Workers isn’t built for, and the limit is telling you so.
A Worker isn’t a small server you rent in a region. It’s a function Cloudflare runs everywhere at once, handed a request, handed a box of pre-wired capabilities (env), and billed only for the milliseconds it actually computes.
Start from the Node server: a process holding a port, a proxy in front, a load balancer above, and a bill that runs whether or not anyone shows up. Workers keep the handler and delete everything else. “Scale to zero” stops being a slogan, “global edge function” stops being vague, and you’re left with a genuinely simpler way to ship web apps, APIs, and AI agents.