Build a Reliable Counter with Cloudflare Workers Durable Objects
Learn how to create a stateful counter that survives restarts, scales automatically, and handles concurrent increments using Cloudflare Workers Durable Objects. The guide covers setup, code, deployment, and verification.
07 Jul 2025, 11:48 UTC

Desired Outcome
Implement a simple HTTP counter that increments on every request and persists its value across restarts and scale‑out. The counter should be isolated per counter key, support concurrent increments without race conditions, and expose the current count via a GET request.
Prerequisites
- A Cloudflare account with a Workers plan that includes Durable Objects (e.g., Workers Pro, Enterprise).
- Node.js 20+ and
wranglerCLI installed (npm i -g wrangler). - Basic familiarity with JavaScript/TypeScript and HTTP.
- Access to the Cloudflare dashboard to confirm account plan and view logs.
Project Setup
- Create a new project:
This creates a minimal Workers project scaffold.wrangler generate durable-counter cd durable-counter - Initialize Git (optional but recommended):
git init git add . git commit -m "Initial commit"
Define the Durable Object Class
Create src/CounterDO.ts (or .js if you prefer plain JavaScript). The class manages the counter state in the object’s internal key‑value store.
export class CounterDO {
constructor(state, env) {
this.state = state;
this.env = env;
}
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
if (path === "/increment") {
return await this.increment();
}
if (path === "/value") {
return await this.getValue();
}
return new Response("Not Found", { status: 404 });
}
async increment() {
// Use the object’s internal KV store for atomic updates.
const current = (await this.state.storage.get("count")) ?? 0;
const next = current + 1;
await this.state.storage.put("count", next);
return new Response(`Counter incremented to ${next}`);
}
async getValue() {
const value = (await this.state.storage.get("count")) ?? 0;
return new Response(`Current count: ${value}`);
}
}
Key points:
state.storageprovides a per‑instance key‑value store that is automatically replicated.- All operations are atomic within the object, eliminating the need for external locking.
- The object only exposes two endpoints:
/incrementand/value.
Register the Durable Object in wrangler.toml
Edit wrangler.toml to add the object and bind it to the worker script.
name = "durable-counter"
type = "javascript"
account_id = "<YOUR_ACCOUNT_ID>"
workers_dev = true
route = ""
zone_id = ""
compatibility_date = "2024-10-01"
[durable_objects]
name = "CounterDO"
class_name = "CounterDO"
The [durable_objects] section declares the class name and the binding name used in the worker.
Worker Script to Route Requests to the Durable Object
Create src/index.js (or .ts) that forwards incoming requests to the appropriate Durable Object instance based on a query parameter id.
export default {
async fetch(request, env) {
const url = new URL(request.url);
const counterId = url.searchParams.get("id") || "default";
const objectId = env.CounterDO.idFromName(counterId);
const objectStub = env.CounterDO.get(objectId);
return objectStub.fetch(request);
}
}
Explanation:
- Requests to
https://.workers.dev/?id=foowill be routed to the Durable Object with namefoo. - If
idis omitted, the worker uses a single instance nameddefault. - All traffic passes through the same worker script, keeping the routing logic simple.
Build and Deploy
- Publish the worker and Durable Object:
This command bundles the project, uploads to Cloudflare, and registers the Durable Object class.wrangler publish - Verify deployment in the dashboard:
- Navigate to "Workers & Pages" → "Durable Objects" and confirm the
CounterDOappears. - Check the worker’s route or Workers‑Dev URL to ensure it’s active.
- Navigate to "Workers & Pages" → "Durable Objects" and confirm the
Testing the Counter
Use curl or a browser to interact with the counter. Replace https://.workers.dev with the actual URL.
# Increment the counter
curl "https://.workers.dev/?id=mycounter&url=/increment"
# Retrieve current value
curl "https://.workers.dev/?id=mycounter&url=/value"
The first request returns Counter incremented to 1, the second Current count: 1. Subsequent increments will increase the number accordingly.
Expected Checks and Verification
- Atomicity: Send multiple concurrent
/incrementrequests using a tool likeheyorwrkand confirm the final count equals the number of requests. - Persistence: Restart the worker (e.g., redeploy or scale down) and verify the counter value remains unchanged by querying
/value. - Isolation: Use two different
idvalues and confirm their counters do not interfere. - Check the Cloudflare dashboard’s Durable Object logs or use
wrangler logs --tailto see state changes in real time.
Recovery & Rollback Options
- Rollback Deployment: If the new worker causes issues, use
wrangler deploy --previewto test in preview mode before publishing. - Delete a Durable Object Instance: To reset a counter, send a DELETE request to
/incrementwith a custom header or use the Cloudflare dashboard’s object inspector to remove the keycount. - Re‑create Durable Object Class: If the class registration fails, delete the existing class via the dashboard and run
wrangler publishagain.
Limitations and Practical Tips
- Durable Objects have a maximum request latency of ~500 ms. Long‑running logic should be avoided inside the
fetchhandler. - Concurrent updates are handled atomically, but if you need to perform multiple related operations (e.g., read-modify-write across different keys), consider using optimistic locking with
state.storage.getWithMetadataandstate.storage.putWithMetadata. - The internal KV store is eventually consistent across replicas. For read‑heavy workloads, consider binding a Workers KV namespace for caching the counter value.
- Durable Objects are separate from Workers KV. If you need a global cache or shared state across all counters, bind a KV namespace in
wrangler.tomland use it inside the DO class. - Always monitor the
Durable Objectlatency and error rates in the Cloudflare analytics dashboard to catch potential hotspots.
Conclusion
By leveraging Cloudflare Workers Durable Objects, you can build a lightweight, stateful counter that scales automatically and maintains consistency without an external database. The pattern demonstrated here—isolated per‑key state, atomic increments, and simple HTTP routing—serves as a foundation for more complex stateful services such as counters, counters with thresholds, or per‑user counters.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.