A Guide to Stateful Serverless with Durable Objects

Grid Image

The Old Way vs The New Way Why Stateful Serverless Matters

Let’s be honest. If you’ve worked with serverless functions, you’ve felt the magic. You write your code, you deploy it, and it just… scales. No servers to patch, no infrastructure to manage. It’s a dream. But then, you hit a wall. A big, stateless wall.

durable objects vs traditional serverless

The Stateless Headache

Traditional serverless platforms, like AWS Lambda or even standard Cloudflare Workers, are designed to be stateless. Think of them as having short-term memory loss. They wake up, do a single job, and then forget everything that just happened. This is fantastic for simple, repeatable tasks, but what happens when you need to remember something? What if you need to keep track of items in a shopping cart, count votes in a live poll, or manage the state of a collaborative document?

That’s when the headache begins. The typical solution is to reach for an external database. You might set up a Redis cache, a FaunaDB instance, or a traditional SQL server. Suddenly, your beautifully simple serverless architecture isn’t so simple anymore. You’re now dealing with:

  • Increased Latency: Your function, running at the edge near the user, now has to make a round trip to a centralized database, which could be halfway across the world.
  • Complex Connections: You have to manage database connections, credentials, and potential connection limits.
  • Scaling Pains: You have to worry about scaling your database region-by-region to keep it close to your users, which adds a whole new layer of operational complexity.

It feels like taking one step forward with serverless, and two steps back into the world of complex infrastructure.

A Paradigm Shift: What Are Durable Objects?

This is where Durable Objects change the game. They represent a fundamental shift in how we think about state in a serverless world.

So, what are they? Forget thinking about them as just another function. Think of a Durable Object not as a function, but as a long-lived, stateful instance of an object that lives on the edge. It’s like giving your serverless function a perfect, persistent memory.

The genius of Durable Objects is that they combine compute and storage in the exact same place. Each object is a self-contained unit with its own private, built-in storage. When your code needs to read or write state, it’s not making a slow network call to a separate database; it’s talking to storage that is right there, attached to it.

This brings us to the key promise of Durable Objects: bringing state closer to the user, dramatically reducing latency and complexity. You get the scalability of serverless with the power of statefulness, all without the headache of managing a separate database. It’s the missing piece of the puzzle that finally allows us to build truly powerful, real-time, and collaborative applications entirely on the edge.

The Core Concepts You Absolutely Need to Know

Now that you understand why Durable Objects are such a big deal, let’s pull back the curtain and look at the three simple but powerful concepts that make them work. Once you grasp these, you’ll be ready to build almost anything.

durable objects concepts ### **A Unique Identity for Every Object**

Every Durable Object has its own unique address, or ID. This is the secret sauce that lets you talk to a specific instance of an object from anywhere in the world. Think of it like a phone number for your data. No matter where a request comes from—a user in Tokyo or a user in London—if they use the same ID, they’ll be talking to the exact same object.

You have two ways to get an ID:

  1. Let Cloudflare Generate One: You can ask Cloudflare to create a brand new, unique ID for you. This is perfect for when you need a new object for a new user or a new session.
  2. Create an ID from a Name: This is incredibly powerful. You can create a consistent ID from a string you already know. For example, you could create an ID for a chat room using idFromName("chatroom-A"). Every time you use that same string, you’ll get a stub that points to the exact same chat room object. This is how you ensure all users in “chatroom-A” are connected to the same place.

The Magic of Single-Threaded Execution

Have you ever had two users try to update the same piece of data at the exact same time? This is called a “race condition,” and it’s a classic source of bugs and corrupted data. With traditional systems, you have to write complex locking logic to prevent this.

Durable Objects solve this elegantly. Each Durable Object processes incoming requests in a single-threaded queue.

What does that mean for you? It means no more data corruption from race conditions. The first request to change a value is guaranteed to finish completely before the next one even starts. It’s like forming an orderly line—one at a time, please! This gives you strong consistency, built right in, without you having to write a single line of locking code.

The Built-in, Super-Fast Storage API

This is where the magic really happens. Every Durable Object comes with its own private, persistent storage. You interact with it through a simple API that will feel instantly familiar:

  • state.storage.get('key'): Reads a value from storage.
  • state.storage.put('key', 'value'): Writes a value to storage.
  • state.storage.delete('key'): Deletes a value.

The key thing to remember is that this storage is physically co-located with your code. It’s not in some far-off database. This means that accessing your data is incredibly fast, with near-zero latency. This combination of strong consistency and high performance is what makes Durable Objects so uniquely powerful for building responsive, stateful applications.

Your First Durable Object A Hands-On Tutorial

Theory is great, but there’s no substitute for getting your hands dirty. Let’s build your very first Durable Object. We’ll create a simple, persistent counter that you can increment from anywhere in the world. This will show you just how easy it is to get up and running.

What You’ll Need

Before we start, make sure you have a few things ready:

  • A free Cloudflare account. If you don’t have one, you can sign up in a couple of minutes.
  • Node.js and npm installed on your machine.
  • The Wrangler CLI, which is Cloudflare’s command-line tool for managing Workers. You can install it with a single command: npm install -g wrangler. For more details, check out the official Wrangler installation guide.
Building a Stateful Serverless App

Step 1: Scaffolding Your Project

Cloudflare provides a simple command to generate all the boilerplate code you need. Open your terminal and run:

npm create cloudflare@latest my-durable-object-project

Wrangler will ask you a few questions. Make sure to select the “Hello World” Worker + Durable Object template. This will create a new directory called my-durable-object-project with two key files we care about: wrangler.toml (your project’s configuration file) and src/index.ts (where our code will live).

Step 2: Defining Your Durable Object Class

Open up src/index.ts. You’ll see a basic Durable Object class. Let’s simplify it to a basic counter. It’s just a standard JavaScript/TypeScript class.

// src/index.ts

export class Counter extends DurableObject {

constructor(state, env) {

super(state, env);

}

// Handle HTTP requests from clients.

async fetch(request: Request) {

// For now, we'll just return a simple message.

return new Response("Hello from the Counter Durable Object!");

}

}

Step 3: Storing the Count

Right now, our counter doesn’t actually count or store anything. Let’s fix that by using the built-in storage API. We’ll modify the fetch handler to read the current value, increment it, save it back to storage, and then return the new value.

// src/index.ts

export class Counter extends DurableObject {

constructor(state, env) {

super(state, env);

}

async fetch(request: Request) {

// Get the current value from persistent storage, defaulting to 0 if it doesn't exist.

let value: number = (await this.state.storage.get("value")) || 0;

// Increment the value.

value++;

// Save the new value back to persistent storage.

await this.state.storage.put("value", value);

// Return the new value.

return new Response(`The count is now: ${value}`);

}

}

// … (keep the main Worker export default part)

**Step 4: Configuring wrangler.toml

Now we need to tell Cloudflare about our Counter class. Open wrangler.toml and make sure you have a [durable_objects] section that looks like this. The create command should have done this for you, but it’s good to verify.

wrangler.toml

name = “my-durable-object-project”

main = “src/index.ts”

compatibility_date = “2023-10-30”

[[durable_objects.bindings]]

name = “COUNTER”

class_name = “Counter”

  • name = "COUNTER": This is the name we’ll use in our Worker code to refer to this type of Durable Object.
  • class_name = "Counter": This tells Wrangler which exported class to use for this binding.

Step 5: Accessing the Object from Your Worker

Finally, we need to write the code in our main Worker to actually use the Durable Object. The export default section in src/index.ts is our main Worker. We’ll modify it to get a specific instance of our counter and forward the request to it.

// src/index.ts

// … (Counter class from above)

export default {

async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

// We'll use a fixed ID for this example, so we always get the same counter instance.

let id = env.COUNTER.idFromName("my-first-counter");

// Get the Durable Object stub for this ID.

let stub = env.COUNTER.get(id);

// Forward the request to the Durable Object.

return await stub.fetch(request);

},

};

Step 6: Deploy and Test!

That’s it! Time to see it in action. In your terminal, run:

wrangler deploy

Wrangler will build and deploy your code, giving you a URL. Open that URL in your browser. The first time you visit, you should see “The count is now: 1”. Refresh the page, and you’ll see “The count is now: 2”. Refresh again, and it will be 3.

Congratulations! You’ve just built a stateful serverless application. The count is being persisted inside the Durable Object, surviving between requests, all running on the edge.

From Simple Counters to Powerful Applications Real-World Use Cases

A simple counter is a great way to learn, but the real power of Durable Objects is in building complex, real-world applications that were previously difficult or impossible to create with a traditional serverless model. Let’s explore some of the exciting possibilities.

Real-time Collaboration Apps (like Figma or tldraw)

This is the killer use case for Durable Objects. Imagine a collaborative whiteboard application like tldraw (which is actually built on Durable Objects!). How do you keep every user’s screen perfectly in sync as people draw shapes, move text, and edit content?

With Durable Objects, the solution is incredibly elegant: each document or whiteboard becomes its own Durable Object. This object acts as the single source of truth. All connected users open a WebSocket connection to the same object instance. When one user makes a change, they send a message to the object, which then updates its state and broadcasts the change to every other connected user. It’s fast, consistent, and scales beautifully without you having to manage a complex real-time backend.

Chat Rooms and Notification Systems

A chat room is a perfect one-to-one mapping for a Durable Object. You can create a unique object for each chat channel using idFromName("channel-dev-talk"). This object’s job is to:

  • Maintain a list of currently connected users (via WebSockets).
  • Receive incoming messages from one user.
  • Store the message in its persistent storage.
  • Broadcast the new message to all other users in the room.

This pattern is far simpler and more efficient than polling a central database for new messages.

Booking Engines and Inventory Management

How do you prevent two people from booking the last available hotel room at the exact same time? This is a classic concurrency problem. Durable Objects solve this out of the box.

You can represent each bookable item—a specific hotel room on a specific night, a seat on a flight, or a ticket to a concert—as its own Durable Object. Because every object processes requests serially (one at a time), it’s impossible to double-book. The first request to book the room will be processed, the state will be updated to ‘unavailable’, and any subsequent requests will be rejected. No complex database transactions or locks are needed.

User-Specific Data and Shopping Carts

Any time you need to manage state for a specific user, a Durable Object is a great fit. A user’s shopping cart is a prime example. You can create a unique Durable Object for each user’s session.

When a user adds an item to their cart, the request goes to their specific cart object. This provides a lightning-fast experience because the data is stored right there at the edge, close to the user. It also simplifies your architecture, as you no longer need a massive, centralized database to manage millions of individual shopping carts.

The Advanced Toolbox Go Beyond the Basics

Once you’ve mastered the fundamentals, Durable Objects offer a powerful set of advanced tools that let you tackle even more sophisticated challenges. Let’s look at a few of the most important ones.

advanced tools for durable objects

Real-time Magic with WebSockets

We’ve talked about real-time applications, and WebSockets are the technology that makes them possible. Durable Objects have first-class support for WebSockets, allowing a single object to act as a WebSocket server for multiple clients.

This is a game-changer. You no longer need a separate, stateful server to manage WebSocket connections. Your Durable Object can accept WebSocket connections, keep track of all connected clients, and broadcast messages between them. This dramatically simplifies the architecture for building chat apps, live-updating dashboards, and collaborative tools.

Scheduling Future Tasks with Alarms

What if you need to run some code in the future? For example, maybe you want to automatically delete a chat room if it’s been inactive for 24 hours, or send a follow-up notification to a user an hour after they sign up. This is where Alarms come in.

The state.storage.setAlarm() method allows you to schedule a time in the future for your Durable Object to wake up and run its alarm() handler. It’s a reliable, built-in cron-like system for your objects. You can schedule one-off events or recurring tasks, giving you a powerful way to manage the lifecycle of your objects and trigger time-based events without any external schedulers.

A Database in Your Object? Enter SQLite

For most use cases, the simple key-value storage API is all you need. But what if you need to store more complex, relational data inside a single object? Cloudflare has you covered with the powerful integration of SQLite directly into Durable Objects.

This allows you to run complex SQL queries—joins, aggregations, and transactions—on a private, file-based database that lives right alongside your object’s code. It gives you the power of a relational database with the low-latency benefits of co-located storage. For a deep dive into this powerful feature, check out Cloudflare’s blog post on the topic.

Durable Objects vs The World A Quick Comparison

It can be helpful to see a direct comparison between the new paradigm of Durable Objects and the traditional way of handling state in serverless applications. Here’s a quick breakdown:

Feature Traditional Serverless + External DB Durable Objects
Latency High (Network round-trip to a separate, often centralized, DB) Ultra-Low (Storage is co-located with the compute logic)
Consistency Model Varies by DB (Often eventually consistent, requires careful setup) Strongly Consistent (Single-threaded execution guarantees order)
Complexity High (Manage DB connections, credentials, scaling, and regions) Low (State is built-in, no external infrastructure to manage)
Ideal Use Case Simple, stateless tasks; applications tolerant of higher latency. Real-time apps, coordination tasks, stateful APIs, anything latency-sensitive.

Frequently Asked Questions FAQ

How much do Durable Objects cost?

Durable Objects have a generous free tier, and beyond that, you are billed based on a combination of three factors: the number of requests, the duration the object is active (i.e., in-memory), and the amount of storage used. The pricing is designed to be cost-effective, especially compared to managing a separate, globally distributed database. For the most up-to-date details, always check the official Cloudflare Workers pricing page.

How do I see logs or debug my Durable Object?

You can debug Durable Objects just like any other Worker. The wrangler tail command is your best friend here. It provides a real-time stream of logs and exceptions from your live Worker and its Durable Objects, making it easy to see what’s happening as you test your application.

What are the limitations I should be aware of?

While incredibly powerful, there are a few limits to keep in mind. There are limits on how much data you can store per object, the frequency of storage operations, and the amount of memory an object can consume. These limits are quite high for most use cases, but it’s always a good idea to review the official limits documentation when designing your application.

Conclusion You8217re Ready to Build the Future

We’ve journeyed from the headaches of stateless serverless to the elegant, powerful world of stateful applications on the edge. By now, you should have a solid grasp of what makes Cloudflare’s Durable Objects so revolutionary. They offer a unique blend of simplicity, performance, and consistency that unlocks a new class of applications.

Gone are the days of complex, high-latency workarounds. With Durable Objects, you can build real-time, collaborative, and stateful systems with ease, all while enjoying the scalability and zero-ops benefits of the serverless model.

The era of stateful serverless is here. What will you build?

Your Digital Journey deserves a great story.

Build one with us.

Cookies Icon

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information to improve and customize your browsing experience, as well as for analytics.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference.