Build a counter
Build a counter using Durable Objects and Workers with RPC methods.
This example shows how to build a counter using Durable Objects and Workers with RPC methods that can print, increment, and decrement a name provided by the URL query string parameter, for example, ?name=A.
import { DurableObject } from "cloudflare:workers";
// Workerexport default { async fetch(request, env) { let url = new URL(request.url); let name = url.searchParams.get("name"); if (!name) { return new Response( "Select a Durable Object to contact by using" + " the `name` URL query string parameter, for example, ?name=A", ); }
// A stub is a client Object used to send messages to the Durable Object. let stub = env.COUNTERS.getByName(name);
// Send a request to the Durable Object using RPC methods, then await its response. let count = null; switch (url.pathname) { case "/increment": count = await stub.increment(); break; case "/decrement": count = await stub.decrement(); break; case "/": // Serves the current value. count = await stub.getCounterValue(); break; default: return new Response("Not found", { status: 404 }); }
return new Response(`Durable Object '${name}' count: ${count}`); },};
// Durable Objectexport class Counter extends DurableObject { async getCounterValue() { let value = (await this.ctx.storage.get("value")) || 0; return value; }
async increment(amount = 1) { let value = (await this.ctx.storage.get("value")) || 0; value += amount; // You do not have to worry about a concurrent request having modified the value in storage. // "input gates" will automatically protect against unwanted concurrency. // Read-modify-write is safe. await this.ctx.storage.put("value", value); return value; }
async decrement(amount = 1) { let value = (await this.ctx.storage.get("value")) || 0; value -= amount; await this.ctx.storage.put("value", value); return value; }}import { DurableObject } from "cloudflare:workers";
export interface Env { COUNTERS: DurableObjectNamespace<Counter>;}
// Workerexport default { async fetch(request, env) { let url = new URL(request.url); let name = url.searchParams.get("name"); if (!name) { return new Response( "Select a Durable Object to contact by using" + " the `name` URL query string parameter, for example, ?name=A", ); }
// A stub is a client Object used to send messages to the Durable Object. let stub = env.COUNTERS.get(name);
let count = null; switch (url.pathname) { case "/increment": count = await stub.increment(); break; case "/decrement": count = await stub.decrement(); break; case "/": // Serves the current value. count = await stub.getCounterValue(); break; default: return new Response("Not found", { status: 404 }); }
return new Response(`Durable Object '${name}' count: ${count}`); },} satisfies ExportedHandler<Env>;
// Durable Objectexport class Counter extends DurableObject { async getCounterValue() { let value = (await this.ctx.storage.get("value")) || 0; return value; }
async increment(amount = 1) { let value: number = (await this.ctx.storage.get("value")) || 0; value += amount; // You do not have to worry about a concurrent request having modified the value in storage. // "input gates" will automatically protect against unwanted concurrency. // Read-modify-write is safe. await this.ctx.storage.put("value", value); return value; }
async decrement(amount = 1) { let value: number = (await this.ctx.storage.get("value")) || 0; value -= amount; await this.ctx.storage.put("value", value); return value; }}from workers import DurableObject, Response, WorkerEntrypointfrom urllib.parse import urlparse, parse_qs
# Workerclass Default(WorkerEntrypoint): async def fetch(self, request): parsed_url = urlparse(request.url) query_params = parse_qs(parsed_url.query) name = query_params.get('name', [None])[0]
if not name: return Response( "Select a Durable Object to contact by using" + " the `name` URL query string parameter, for example, ?name=A" )
# A stub is a client Object used to send messages to the Durable Object. stub = self.env.COUNTERS.getByName(name)
# Send a request to the Durable Object using RPC methods, then await its response. count = None
if parsed_url.path == "/increment": count = await stub.increment() elif parsed_url.path == "/decrement": count = await stub.decrement() elif parsed_url.path == "" or parsed_url.path == "/": # Serves the current value. count = await stub.getCounterValue() else: return Response("Not found", status=404)
return Response(f"Durable Object '{name}' count: {count}")
# Durable Objectclass Counter(DurableObject): def __init__(self, ctx, env): super().__init__(ctx, env)
async def getCounterValue(self): value = await self.ctx.storage.get("value") return value if value is not None else 0
async def increment(self, amount=1): value = await self.ctx.storage.get("value") value = (value if value is not None else 0) + amount # You do not have to worry about a concurrent request having modified the value in storage. # "input gates" will automatically protect against unwanted concurrency. # Read-modify-write is safe. await self.ctx.storage.put("value", value) return value
async def decrement(self, amount=1): value = await self.ctx.storage.get("value") value = (value if value is not None else 0) - amount await self.ctx.storage.put("value", value) return valueFinally, configure your Wrangler file to include a Durable Object binding and migration based on the namespace and class name chosen previously.
{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-counter", "main": "src/index.ts", "durable_objects": { "bindings": [ { "name": "COUNTERS", "class_name": "Counter" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": [ "Counter" ] } ]}name = "my-counter"main = "src/index.ts"
[[durable_objects.bindings]]name = "COUNTERS"class_name = "Counter"
[[migrations]]tag = "v1"new_sqlite_classes = ["Counter"]Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-