Many applications need to maintain shared state across multiple requests. A shopping cart must preserve its contents as users navigate between pages. A multiplayer game needs to coordinate player actions. A rate limiter must track requests even when they arrive from different server instances.
These requirements become difficult when application code runs across multiple distributed workers. Each worker may handle a different request, and ordinary in-memory variables are not automatically shared between instances.
One approach is to store shared state in a database or coordinate access through an external service. However, this can introduce additional network calls and make synchronization more complicated.
Cloudflare Durable Objects provide another option. They allow developers to create stateful objects with a stable identity, a single logical point of coordination, and access to persistent storage. Requests associated with the same object are coordinated through that object's execution model, making it easier to manage shared state without building a separate coordination service for every use case.
This article explains how Durable Objects work, how to implement a basic stateful service, and what to consider when using them in production.
Why Shared State Is Difficult in Distributed Applications
Consider an application that maintains a counter for each customer.
A basic implementation might use an in-memory variable:
let counter = 0;
export default {
async fetch() {
counter++;
return new Response(
JSON.stringify({ counter }),
{
headers: {
"Content-Type": "application/json"
}
}
);
}
};This example works as a simple demonstration, but it does not provide reliable shared state in a distributed environment.
Different worker instances can have different values of counter. An instance may also be restarted, losing its in-memory value. Concurrent requests handled by different instances do not automatically coordinate their updates.
Moving the counter into a database can solve persistence problems, but applications may still need to manage concurrent updates and additional network requests.
A stateful coordination mechanism can simplify these requirements.
What Are Cloudflare Durable Objects?
Cloudflare Durable Objects are named, addressable objects that combine application logic with state and coordination.
Each Durable Object has a unique identity within its namespace. Requests routed to the same object are handled through that object's execution model, allowing it to coordinate operations on shared state.
A Durable Object can use persistent storage to preserve data beyond the lifetime of an individual request or in-memory object instance.
For example, an application might create separate Durable Objects for:
- Individual shopping carts.
- Multiplayer game rooms.
- Collaborative document sessions.
- Per-customer rate limits.
- Real-time notification channels.
- Shared counters and coordination tasks.
A shopping application could assign one object to each cart. Requests that update the same cart are routed to the same logical object, while requests for different carts can be handled independently.
This model is useful when state needs a clear owner and updates need to be coordinated.
How Durable Objects Work
A typical Durable Objects implementation has three parts.
1. The Worker Receives a Request
A Cloudflare Worker receives the incoming HTTP request. It determines which Durable Object should handle the operation.
For example, a request containing a cart ID can be routed to the object associated with that cart.
2. The Worker Obtains the Object Stub
The Worker uses a Durable Object namespace to identify an object and obtain a stub.
The stub acts as a communication interface to the selected object. The Worker can send requests through it without directly managing the object's runtime lifecycle.
3. The Durable Object Processes the Request
The Durable Object handles the request and reads or updates its state.
Its execution model provides coordination for requests handled by the same object. Persistent storage can be used to preserve state across restarts.
The following diagram summarizes the flow:
Client Request
|
v
Cloudflare Worker
|
v
Select Durable Object ID
|
v
Durable Object Stub
|
v
Durable Object Logic
|
v
Persistent Storage
|
v
Response to ClientThe important design decision is choosing the object identity. Requests that must coordinate should use the same object ID, while independent state should be assigned to different objects.
Building a Persistent Counter
A counter is a useful way to understand the basic programming model.
The following example uses the current-style JavaScript Durable Objects API with persistent storage through ctx.storage. It illustrates the core implementation, while deployment configuration and compatibility should be checked against the Cloudflare Workers runtime used by the project.
Step 1: Implement the Durable Object
Create a class named Counter:
export class Counter {
constructor(ctx, env) {
this.ctx = ctx;
this.env = env;
}
async fetch(request) {
const current = await this.ctx.storage.get("count") ?? 0;
const next = current + 1;
await this.ctx.storage.put("count", next);
return Response.json({
count: next
});
}
}The object reads the current count from persistent storage, increments it, and saves the updated value.
Unlike a plain global variable, the stored value can survive an object restart.
This example assumes that every request increments the counter. A production API would usually separate read and update operations, validate the HTTP method, and handle errors explicitly.
Step 2: Route Requests to the Object
The Worker can select a named Durable Object and send it a request:
export default {
async fetch(request, env) {
const id = env.COUNTERS.idFromName("global-counter");
const stub = env.COUNTERS.get(id);
return stub.fetch(request);
}
};Here, COUNTERS is a Durable Object namespace binding configured in the Worker environment.
Using the same name, global-counter, routes requests to the same logical object within that namespace.
For a customer-specific counter, the name could instead be derived from a validated customer identifier.
Do not use arbitrary client-provided names without validation and authorization. Otherwise, users may be able to create or access object identities that the application did not intend to expose.
Step 3: Configure the Binding
The Worker must declare its Durable Object binding and class in the deployment configuration. The exact configuration syntax depends on the project's Cloudflare tooling and migration setup.
A configuration typically identifies the binding name, the exported Durable Object class, and the applicable migration entry.
After configuring the binding, deploy the Worker and test requests against the deployed environment.
A local development test is useful, but it should not replace testing the actual deployment configuration and runtime behavior.
Maintaining Consistency Across Concurrent Requests
Durable Objects are particularly useful when several requests update the same logical state.
Suppose two requests arrive to increment a counter. If each request reads the same old value and writes an independently calculated result, an ordinary read-modify-write sequence could lose an update in a system without proper coordination.
Join the conversation! Your thoughts help the community grow.