Sketio

Cloudflare Durable Objects Real-Time Collaboration

Updated

A Durable Object is a special kind of Worker that has a name and storage of its own. Every request for a given name reaches the same single instance, wherever in the world it comes from, and that instance runs code on one thread. This is what makes real-time features simple: a chat room or a shared document is one object, everyone in it talks to the same code, and nothing needs a lock.

This template shows a room. Two clients connect with WebSockets, a Worker routes each connection to the room's Durable Object by name, and the object keeps its history in SQLite storage and uses an alarm for housekeeping.

getByNamesql.execClient AClient BWorkerRoom Durable ObjectSQLite storageAlarmWebSocket upgradesame room namesetAlarm

Scroll sideways to see the whole diagram

Cloudflare Durable Objects Real-Time Collaboration. Open it in Sketio to change it.

Start from this diagram and edit it on your own board.

By continuing, you agree to the Terms of Service and Privacy Policy, including sending images of your strokes, diagram labels and similar data to providers in the United States (Cloudflare, Inc. and TypeSafe AI, Inc.) for AI conversion.

What each part does

Client A
A browser or app that opens a WebSocket and sends the name of the room it wants to join.
Client B
Another client in the same room. It reaches the same object because it asks for the same name.
Worker
The stateless entry point. It checks that the request asks for a WebSocket upgrade, picks the object with env.ROOMS.getByName(roomName) and passes the request on with stub.fetch(request). It is also where you check who the user is.
Room Durable Object
One instance per room name. It creates a WebSocketPair, keeps one end with ctx.acceptWebSocket(), and handles messages in webSocketMessage() and disconnects in webSocketClose(). To broadcast, it loops over ctx.getWebSockets(). The same name always leads to the same object, which is first created near where it is first requested.
SQLite storage
Storage that belongs to this one object, strongly consistent and transactional. The object reads and writes it with ctx.storage.sql.exec(), and transactionSync() groups statements so they are rolled back together if the callback throws. Keep anything that must survive here, because the memory of an evicted object is lost.
Alarm
A wake-up the object sets for itself with setAlarm(). Each object has one alarm at a time, and the alarm() method runs when it is due. Alarms run at least once: if alarm() throws, it is retried with exponential backoff, starting at 2 seconds, for up to 6 retries.

How a message flows

  1. A client opens a WebSocket to the Worker and names the room.
  2. The Worker checks for the Upgrade: websocket header and forwards the request to the room's object with stub.fetch(request).
  3. The object creates a WebSocketPair, accepts its end with ctx.acceptWebSocket() and returns the other end with status 101. Because it used this Hibernation API, the runtime may evict the object from memory while the connections stay open, and no duration is billed while it sleeps.
  4. A client sends a message. The object wakes if it was asleep (its constructor runs again), appends the message to SQLite and sends it to every socket from ctx.getWebSockets(). One instance handles the room, so all participants see one order.
  5. Per-connection details, such as a display name, go in ws.serializeAttachment(). They survive hibernation, up to 16,384 bytes. Fields kept only in memory do not.
  6. From time to time the object's alarm fires and alarm() trims old messages or closes an empty room, then sets the next alarm if there is more to do.

When to use it

Common variations

Split the work across more objects

A single object handles roughly 500 to 1,000 simple requests per second, less if each request does real work. Split by natural boundaries, such as one object per room, document or user. A single object for the whole app becomes a bottleneck.

Plan for deploys

Deploying new code disconnects every WebSocket. Have clients reconnect on their own, and rebuild their view from what the object has in storage.

Make alarms safe to retry

Catch errors inside alarm() and schedule the next alarm before returning. Otherwise a long outage can use up the retries, and the alarm does not run again until setAlarm() is called.

Recover from a bad write

SQLite-backed objects support point-in-time recovery of their database for the past 30 days. It is not available in local development.

Make it yours

Rename the object after what it coordinates (a room, a document, a game) and decide what its name is. That choice sets how your work is divided.

Opens this diagram as a board you can edit.

By continuing, you agree to the Terms of Service and Privacy Policy, including sending images of your strokes, diagram labels and similar data to providers in the United States (Cloudflare, Inc. and TypeSafe AI, Inc.) for AI conversion.

All templates