Development
Taming Stateful at the Edge: A Real-Time API with Cloudflare Durable Objects
Edge compute is stateless by design, which is the wrong shape for shared state. How Durable Objects solve it, and what that costs.
Edge compute is built on a premise that makes real-time collaboration hard: any request can go to any location, and nothing is remembered between them.
For a separate operational perspective on how teams organise and measure work, this page.
For independent background and broader industry context, see Cloudflare.
That premise is exactly wrong for shared state, and Durable Objects are the resolution. This is how Vaultrice is built and why.
Why stateless is the wrong shape here
Edge functions scale by having no state. Any of hundreds of locations can serve any request, because none of them needs to know anything.
Shared state needs the opposite. Two clients editing the same document must reach something that knows what the other one did. If they land in different places, you need coordination between those places — which is a distributed consensus problem, and it is where projects disappear for months.
And WebSockets make it sharper. A connection is held open at one specific location. Broadcasting to everyone on an object means every holder of a relevant connection must be reachable.
What a Durable Object is
A single-instance, addressable object with its own storage, and the guarantee that matters: for a given id there is exactly one instance, globally.
Two consequences follow, and they are the whole reason to build on this.
No coordination problem. Every client for an object reaches the same instance. There is no consensus to reach because there is nothing to disagree with.
Serialised access. Requests to one object are handled one at a time, so the read-modify-write race that plagues shared counters does not exist inside the object. Its own state is consistent by construction.
And it holds WebSocket connections, so broadcasting to everyone on the object is a local operation rather than a distributed one.
The mapping
A Vaultrice object is a Durable Object. The id you pass to NonLocalStorage addresses it.
const nls = new NonLocalStorage(credentials, 'doc-42')
Which explains several things about the API:
Why there are no queries. You address by id. Finding all objects matching a condition would mean asking every instance, and that is the distributed problem the design avoids.
Why writes to one object are consistent but two objects have no relationship.
Why objects have limits. Each is a real instance with real resources.
Why the id matters so much. It is not a name — it is the routing key.
What it costs
Being honest about the trade.
No cross-object queries or transactions. If your access pattern is relational, this is the wrong foundation and a database is the right one.
Location. An object lives where it was created, near its first user. Great for a document with a local team; less good for one with users on three continents, where someone is always far away.
Per-object limits. Scale is achieved by having many objects, not by one object growing.
Cold starts. An object that has been idle takes a moment to wake. Small, and non-zero.
And a platform dependency, which is a real consideration and should be stated rather than glossed over.
What we built on top
The value is not the primitive; it is not having to use it directly.
A localStorage-shaped API instead of writing a Worker and defining a fetch handler.
Connection lifecycle — reconnection with backoff, resubscription, catching up after a gap.
Presence, which is harder than it looks because the interesting case is a client that vanished without saying so.
Security levels including end-to-end encryption, above the transport.
And offline support, which the primitive does not provide.
None of that is impossible to build. It is several months, and then it is yours to operate.
When to use the primitive directly
Do that if you need custom server-side logic per object, unusual persistence semantics, or you are already deep in the Workers platform and want no dependency.
Use something built on it if you want shared state in an afternoon and the standard semantics fit.
The honest comparison is not whether Durable Objects are good — they are. It is whether the wrapper is worth what it costs relative to the months of building the same layer.
The short version
Edge compute is stateless; shared state is not. Durable Objects resolve it with a single global instance per id.
Two consequences: no coordination problem, and serialised access — so consistency inside an object is structural.
Which explains the API — addressed by id, no queries, per-object limits.
The costs are real: no cross-object queries, fixed location, and a platform dependency.
And the value of a layer on top is the connection lifecycle, presence, security and offline — several months of work, or an afternoon.