Durable Objects — One Name, One Place, One Thread
Back in the KV lesson, Hoplink's live click counter had nowhere to live. A global variable is per isolate; KV accepts about one write per second per key and spreads slowly. What the counter needs is a single, authoritative home that every request, from every city, talks to. That is a Durable Object.
The rule
One name, one object, one thread — reachable from anywhere.
- One name. You ask for an object by a name you choose, such as the short code
cat. The same name always leads to the same object, anywhere in the world. Different names give different objects, socatanddognever contend with each other. - One object. Exactly one instance with that name exists at a time. It runs in one Cloudflare location — usually near where it was first requested.
- One thread. The object handles its requests one event at a time, like a single shop assistant serving a queue. Your code never has two copies of itself racing to change the same field.
A Durable Object is a JavaScript class. Workers reach it through a binding and call its methods directly (remote procedure calls, or RPC):
import { DurableObject } from "cloudflare:workers";
export class LinkCounter extends DurableObject {
count = 0; // lives in this object's memory
async increment() {
this.count += 1;
return this.count;
}
}
export default {
async fetch(request, env, ctx) {
const code = new URL(request.url).pathname.slice(1);
const id = env.COUNTER.idFromName(code); // name → id
const stub = env.COUNTER.get(id); // id → stub
const clicks = await stub.increment(); // call it like a local method
return new Response(`${code} has ${clicks} clicks`);
},
};The config declares the binding and registers the class in a migration:
{
"durable_objects": { "bindings": [{ "name": "COUNTER", "class_name": "LinkCounter" }] },
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["LinkCounter"] }]
}One warning already: count here is only in memory. When the object goes idle, it can be evicted and count resets to 0. The persistence lesson fixes that.
The shortcut
To design with Durable Objects, ask "What is the unit of coordination?" — the smallest thing whose state must be exactly right. Make that the object's name. One object per link, per chat room, per user, per game; never one object for the whole app.
Find the unit
Each link's count must be exact; different links never affect each other. Unit: one link.
Trap: one giant object for everything. Because an object handles one event at a time, making it the single door for the whole app turns it into a bottleneck.
Drills
Answer fast — trust the rule.
Two visitors in Paris and Sydney both open hop.example/cat. How many LinkCounter objects named cat handle them?
What does env.COUNTER.idFromName("cat") do?
Why is one Durable Object per link better than one for the whole site?
A LinkCounter keeps count only in a class field. What happens after it sits idle for a while?
Remember
- One name, one object, one thread — reachable from anywhere.
- Shortcut: name objects by the unit of coordination, never the whole app.
- Trap: trusting class fields to survive, and one object for everything.
