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";
// Worker
export 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 Object
export 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>;
}
// Worker
export 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 Object
export 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, WorkerEntrypoint
from urllib.parse import urlparse, parse_qs
# Worker
class 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 Object
class 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"
]
}
]
}"$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" ]