Cloudflare, in pieces
Chapters

Cloudflare / 03

Durable Objects,
from the inside.

Some state has to be agreed on: the last seat, the next move, the current revision. A Durable Object gives each such thing one coordinator with its own storage, found by name. Run one.

3 scenarios · 2 instruments · A runnable example

Illustration One room, several clients

  • One room. Tabs that join room:heron reach one object, chosen by the Worker with ROOMS.getByName("heron"); a tab that moves to room:fox reaches a different object. A turn saved to SQLite survives hibernation: the next request runs the constructor and reloads it. A turn kept only in working memory is gone. Connections stay open through hibernation only when the object accepted them with ctx.acceptWebSocket(); an object holding standard sockets does not hibernate, and a shutdown terminates them.
  • The last seat. Two Workers that each read one open seat and then write zero accept two players for one seat. One object that checks and reserves without yielding accepts one and answers “room full” to the other.
  • Several rooms. Six messages for three rooms take two rounds with one object per room, six with one object for everything. A round is a turn in this model, not a measurement.

Illustration Stop at the await

Two calls to increment() run in one object. If A reads the counter, awaits an external service, and B runs and writes during that wait, A writes back its old value plus one: one change is lost. When the handler awaits first and then updates with one SQL statement, the same order keeps both changes.

An external await is not a lock: while A waits, another event can run in the object. Input and output gates protect storage operations, not an outside fetch(). Writes with no await between them commit together; transactionSync draws an explicit commit-or-rollback boundary, as the example does for its revision and command receipt.

The runnable example: three files

A small Worker and object that persist a revision, reject a stale move, and remember a command ID so a retry does not apply it twice. Save the files in a new folder and follow try.sh. The Worker accepts only local requests, and this page never runs it. Before public use it needs identity, per-room permissions, limits, and a retention policy for receipts. When you change a deployed class, add migrations after room-v1 and never edit an applied one.

worker.js · the Worker and the object Download ↓

wrangler.jsonc · the binding and the migration Download ↓

try.sh · the local commands Download ↓

Read the mechanism

One name leads to one object

A class defines the behavior; a namespace binding names the collection of objects of that class. getByName() returns a stub, and the same name in the same namespace reaches the same object wherever the caller is. Cloudflare creates an object near the first request for it, and today an object does not move after that. A location hint is best effort; a jurisdiction is strict.

The code can stop; the saved state stays

Each SQLite-backed object has its own private database through ctx.storage.sql, recommended for new classes. When an object hibernates or is evicted, its working memory is discarded, and the next event runs the constructor again. There is no shutdown hook, so save when the state changes.

Connections and alarms

Sockets accepted with ctx.acceptWebSocket() stay connected while the object hibernates; with ws.accept() the object stays in memory, and a shutdown such as a deploy terminates them. An object has at most one alarm at a time; its handler runs at least once and is retried when it throws, so make it safe to repeat.

Scale across rooms, not copies of one room

One object handles its events one at a time. A single object for everything becomes a bottleneck; one object per room, document or user lets unrelated work proceed separately. Separate objects’ databases never become one transaction. blockConcurrencyWhile is for initialization and migrations, not for holding requests around slow outside work.

Sources and limits

Cloudflare pages used

Documented and illustrated

Left out on purpose