Finally, Durable Objects Click

Cloudflare Developers| 00:09:28|Apr 29, 2026
Chapters14
An introduction to durable objects, explaining what they are, how they work, and why they’re gaining attention, including the speaker’s gradual path to understanding them.

Durable Objects become stateful serverless primitives: per-room objects with WebSockets enable real-time apps at scale.

Summary

Cloudflare Devs’ Ylvis introduces Durable Objects as stateful serverless functions that map one ID to one object. The demo builds a tiny Slack-like chat with persistent SQLite-backed state and demonstrates live updates by tying a room ID to a dedicated durable object. The code shows how to define a durable object, how to route requests, and how to store/retrieve messages. WebSockets are added so every client in a room talks to the same object and receives broadcasts instantly, without Redis or a separate WebSocket server. The front end is updated to open a WebSocket connection per room and push new messages as they arrive. Ylvis emphasizes that this pattern scales best when you split the app into many independent durable objects (one room, one object), not a single monolithic object. He also notes practical caveats like authentication and rate limiting that aren’t the focus of the demo. The takeaway is that durable objects provide storage, code execution, and live connections in one natural unit, enabling real-time multiplayer scenarios with per-object consistency.

Key Takeaways

  • One durable object per meaningful entity (e.g., one chat room maps to one durable object), so all clients in that room share the same state and logic.
  • SQLite-backed durable state is stored in a per-object table (not a full database), enabling simple, durable persistence within the object.
  • WebSockets are natively integrated via a /ws route and a WebSocket pair, allowing live updates without external pub/sub systems.
  • The system uses hibernation (WebSocket auto response) so objects wake only when messages arrive, saving resources while keeping clients connected.
  • Front-end updates replace HTTP POST with WebSocket.send and append incoming messages in real-time, achieving instant visibility across all clients.
  • The demo shows the entire chat room durable object, including SQLite storage, WebSocket broadcasting, and hibernation, in under 70 lines of code.

Who Is This For?

Developers curious about turning per-entity state into scalable, real-time features using Cloudflare Durable Objects. Great for teams considering per-room or per-document objects to replace centralized or Redis-backed pub/sub patterns.

Notable Quotes

"A durable object is a stateful serverless function."
Defines the core mental model that sets Durable Objects apart from regular workers.
"One room ID maps to one durable object."
Explains the fundamental mapping of identity to object and how clients share state.
"There is no Redis pub/sub, no separate WebSocket server, and no cluster to manage."
Highlights the simplicity and locality of the pattern.
"The entire chat room durable object, including SQLite storage, WebSocket broadcasting, and hibernation support is under 70 lines."
Emphasizes the small, elegant surface area of the implementation.
"Same ID, same durable object. Different ID, different durable object."
Reinforces the per-entity scoping principle critical to scaling with durable objects.

Questions This Video Answers

  • How do Durable Objects map IDs to per-entity state in Cloudflare Workers?
  • Can I implement real-time features without a separate WebSocket server using Cloudflare Durable Objects?
  • What performance benefits do I get from hibernation in Durable Objects?
  • How should I split an app into multiple durable objects for scalability?
Durable ObjectsCloudflare WorkersWebSocketsSQLite in Durable ObjectsHibernate WebSocket APIwrangler.json migrationsPer-object stateReal-time applications
Full Transcript
Durable objects. What are they? How do they work? And why is everybody talking about them? And I'll be honest, this one took me a little while to really understand. Not because durable objects are impossible, but because they're kind of a different paradigm. So, in today's video, we're going to make durable objects click. We'll start with a bare minimum durable object, a tiny Slack-style chat app with persistent state. And then we'll build on top of it to add WebSockets so it becomes a real-time application. And and don't leave yet just because it's a chat app. I know it's not a super exciting example, but it's not about a chat app. The point is to understand the durable object pattern. Let's dive in. This is a deployed app. My name is Ylvis. Hello. Send. Let me duplicate this tab. Abby says hello. You'll see that it updates here, but not here. So, I have to click refresh for it to work. Before we look at any code, the first question is, where does that state live? Now, we have 330 data centers in over 120 countries, but let's assume it. I sent my message here from Austin, and the durable object was created in Dallas. Then Abby sent the message from Amsterdam, reaching the same durable object in Dallas. By default, a worker does not give you application state you can rely on. This is an object, but it's not a durable object. Real applications, of course, need state. And yes, you could store data in D1, or you could store data in KV, but real-time applications have another problem. It's not just where do I store the messages, it's also who is currently in the room? Who should receive this new message? How do we make sure everyone in the room is talking to the same thing? That is where durable objects are different. A durable object is a stateful serverless function. It runs code like a worker, but unlike a regular worker, it can also keep durable state. Each durable object has its own private storage. And the important mental model is that one ID maps to one durable object. In our app, one room ID maps to one durable object. Everyone in the general room talks to the same durable object. Everyone in a different room talks to a different durable object. Same ID, same durable object. Different ID, different durable object. Let's see that in code. We'll start with wrangler.json. The first thing you see here is this migrations entry. This tells the runtime that we are having a SQLite durable object class, and the version is one. You only have to do this once when you're creating a durable object, or when you're deleting a durable object, or renaming a durable object. Then we have the durable object itself. We define the binding. We say the class name and the binding name, which in this case is chat_room. Now, let's go to the worker. This is the worker handler. And when we make a post or get request, we extract the room ID with a regex. And this is the key part here. This gets the unique durable object instance, okay? This is the crucial line. And if it's a get request, then we just list all the messages. With a post request, then we call room.send message. Okay, so these are RPCs, remote procedure calls. And as you can see, this is all there is for the worker. So, let's go to this durable object here. So, we export a class called chat room, and it extends a durable object. Now, every time I create a durable object, we basically have an instance of this class. The constructor runs when you initiate the class. And we have to call this block concurrency while block here to make sure no other methods are called before this is done. And in here, we create a new table. Notice I'm not creating a database, I'm creating a table. Next, you have send message. Look how simple this method is. We have the created at, then we create a new row, we just insert into messages, and then we return it. Simple as that. And for get messages, we select all for messages, and we order by created. Now, one more thing I have to show you before we move on is this front end, of course. So, it's a form here. Then we get the room ID, otherwise it's general. We just render a message HTML. We have this load function, which just calls the get messages. And then we submit the form. We make a post request with the username and the content. And then we we call load again. So far, we've seen the first superpower of durable objects, how easy it is to store a little piece of state. But right now, the browser still has to refresh to see new messages. That means we have durable state, but we do not have live updates. The next step is to keep clients connected to the durable object, and this is where WebSockets come in. Every client in the same room can connect to the same durable object. So, when one client sends a message, the durable object can store it, and then send it out to everyone currently connected. So, there is no Redis pub/sub, no separate WebSocket server, and no cluster to manage. The room already has a one object responsible for its state. We're going to let that same object handle the live connections, too. So, let's add WebSockets. Let me walk you through the code. We have to change three things. First, we now have an additional path /ws for the WebSocket upgrade. And if it's WebSocket, we forward the request to the durable object. Okay, so this is important. We object. And if it's a get request, we get the current messages. This is on the initial load. Our worker barely changed. We just added the WebSocket support here. Now, what about the durable object? The durable object had a few changes, but not as many as you might expect. So, first, we added this set WebSocket auto response thing. This uses the hibernatable WebSocket API. That means the durable object does not need to stay active in memory forever just because the socket is connected. And this is big. When nothing is happening, the durable object can hibernate, but the client stay connected to Cloudflare. And when a message arrives, the durable object wakes up and handles it. We have this new method here, fetch, where we create a new WebSocket pair, which has a client and a server. And this part here is important. We store the server on the context. Next is the most important addition of our WebSockets. That's the WebSocket message call. Anytime you receive a message, this will fire, which is great if the durable object is hibernated, cuz this will wake it up. We'll get the user and the content from the WebSocket message. Then we still insert it into our table, and we take the payload, we take our message. And here is the the magic part. We just loop over all the WebSockets and send that payload. So, if we have 100 connected clients, we would send out 100 messages. Isn't that Isn't that amazing? And then finally, we had to update our front end. When it first loads, we still make that get request that we had initially. Now, we create a new WebSocket, and we add a listener that say, whenever there's a new message, append it to our messages. And instead of doing a post request, we now say WebSocket.send. Refresh here and refresh here. So, here I'll say, "Finley, no way." Send. Instantaneous. And as you can see, live on the internet. To add real-time support, we only changed a few things. We added a WebSocket route. We accepted the WebSocket inside of the durable object. And we changed message sending from HTTP post to WebSocket.send. The entire chat room durable object, including SQLite storage, WebSocket broadcasting, and hibernation support is under 70 lines. This is why durable objects are more than just storage. They are stateful serverless functions. They can store state, they can run code, and they can accept WebSockets. And because requests for the same ID go to the same object, they are natural fit for real-time and multiplayer systems. It's not one durable object for the whole app. That would be the wrong mental model. One room per object. One document per object. One game session per object, or one user session per object. A million chat rooms can be a million durable object IDs, spread across the network. The thing that needs state gets its own durable object. Now, this doesn't mean that every app should become a giant durable object. Again, that is the wrong model. Durable objects scale best when you naturally split the application into many independent objects. You could split by room, like we did here, or by document. Because if you put your entire app behind one durable object, you have created one very important bottleneck. One very busy room is still one very busy object. So, the question is not, how do I put the whole database into a durable object? The question is, what part of my app needs state, real-time connections, and strong per object consistency. Also, this demo leaves out some production concerns. Right? In a real app, you would add authentication, validation, rate limiting, and probably a better data model. But I left those out on purpose. The goal here is to make the durable object mental model obvious. Now, you do not need to move your entire application to Cloudflare in order to use durable objects. You can use them for a specific part of your system, the part that needs durable state, live connections, or per object consistency. Remember, same ID, same durable object. Different ID, different durable object. And once you understand that, the WebSocket example and the chat example start to feel simple. Thanks for watching. I hope that this was useful, and that you can now start building with durable objects. It is an incredible primitive, and there's a reason that D1 and container and a lot of Cloudflare primitives are built on top of it. See you next time.

Get daily recaps from
Cloudflare Developers

AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.