Durable Object
This guide explains how to create, bind and use Cloudflare Durable Objects within your Worker scripts.
Create a Durable Object
Section titled “Create a Durable Object”At a bare minimum, you need to create a DurableObjectNamespace
object as a stable reference to your Durable Object namespace.
import { DurableObjectNamespace } from "alchemy/cloudflare";
const counter = new DurableObjectNamespace("counter", { className: "Counter", // whether you want a sqllite db per DO (usually yes!) sqlite: true,});
If you’re paying close attention, you’ll notice that we call new DurableObjectNamespace
instead of await DurableObjectNamespace
like you might have come to expect from Alchemy Resources.
This is because of oddities in Cloudflare’s API design. Durable Object namespaces are not resources in the traditional sense because they cannot exist without a Worker.
Bind to a Worker
Section titled “Bind to a Worker”Instead, you create a Durable Object namespace and then bind it to your Worker:
export const worker = await Worker("Worker", { name: "my-worker", entrypoint: "./index.ts" bindings: { // bind the Durable Object namespace to your Worker COUNTER: counter, },});
Durable Object Class
Section titled “Durable Object Class”Now, we have a Worker with a Durable Object running within it. To use this Durable Object, our Worker script must include a class for the Durable Object and then some code in the fetch
handler to interact with it.
A simple Durable Object may look like so:
export class Counter { private state: DurableObjectState; private count: number;
constructor(state, env) { this.state = state; this.count = 0; }
async fetch(request) { const url = new URL(request.url); const path = url.pathname;
// Retrieve current count this.count = (await this.state.storage.get("count")) || 0;
if (path === "/increment") { this.count++; await this.state.storage.put("count", this.count); } else if (path === "/decrement") { this.count--; await this.state.storage.put("count", this.count); }
return Response.json({ count: this.count }); }}
Call from a Worker
Section titled “Call from a Worker”Now, our fetch
handler can get a Durable Object instance via the COUNTER
binding:
import { env } from "cloudflare:workers";
export default { async fetch(request: Request) { const url = new URL(request.url);
// Create an ID for the Counter (different IDs = different Counter instances) const id = env.COUNTER.idFromName("A");
// Get a stub for the Counter instance const stub = env.COUNTER.get(id);
// Forward the request to the Durable Object return stub.fetch(request); },};
Type-safe Bindings
Section titled “Type-safe Bindings”Remember, for env.
to be type-safe, you need to configure your src/env.d.ts
to infer the types from your worker:
import type { worker } from "./alchemy.run";
export type WorkerEnv = typeof worker.Env;
declare module "cloudflare:workers" { namespace Cloudflare { export interface Env extends WorkerEnv {} }}
Cross-Script Binding
Section titled “Cross-Script Binding”You can share Durable Objects across multiple Workers, allowing one Worker to access Durable Object instances defined in another Worker. This enables powerful patterns for building distributed systems where different Workers can coordinate through shared state.
Method 1: Using re-exported syntax
Section titled “Method 1: Using re-exported syntax”You can directly reference the Durable Object binding from the provider Worker:
import { Worker, DurableObjectNamespace } from "alchemy/cloudflare";
// Create the provider Worker with the Durable Objectconst host = await Worker("Host", { entrypoint: "./do-provider.ts", bindings: { SHARED_COUNTER: new DurableObjectNamespace("shared-counter", { className: "SharedCounter", sqlite: true, }), },});
// Create the client Worker using the provider's Durable Object binding directlyconst client = await Worker("client", { entrypoint: "./client-worker.ts", bindings: { // Re-use the exact same Durable Object binding from the provider worker SHARED_COUNTER: host.bindings.SHARED_COUNTER, },});
Method 2: Using scriptName
directly
Section titled “Method 2: Using scriptName directly”Alternatively, when creating a Durable Object binding in a client Worker, you can reference a Durable Object defined in another Worker by specifying the scriptName
:
import { Worker, DurableObjectNamespace } from "alchemy/cloudflare";
const hostWorkerName = "host";
const durableObject = new DurableObjectNamespace("shared-counter", { className: "SharedCounter", scriptName: hostWorkerName, sqlite: true,});
// First, create the Worker that defines the Durable Objectconst host = await Worker("host", { entrypoint: "./do-provider.ts", name: hostWorkerName, bindings: { // Define the Durable Object in this worker SHARED_COUNTER: durableObject, },});
// Then, create a client Worker that uses the cross-script Durable Objectconst client = await Worker("client", { entrypoint: "./client-worker.ts", bindings: { // Reference the same Durable Object but specify which script it comes from SHARED_COUNTER: durableObject, },});
Durable Object Provider Implementation
Section titled “Durable Object Provider Implementation”The provider Worker implements the Durable Object class and optionally provides endpoints:
export class SharedCounter { private state: DurableObjectState; private counter: number;
constructor(state: DurableObjectState, env: any) { this.state = state; this.counter = 0; }
async fetch(request: Request): Promise<Response> { const url = new URL(request.url);
// Retrieve current count from durable storage this.counter = (await this.state.storage.get("count")) || 0;
if (url.pathname === "/increment") { this.counter++; await this.state.storage.put("count", this.counter);
return Response.json({ action: "increment", counter: this.counter, worker: "do-provider", }); }
if (url.pathname === "/get") { return Response.json({ action: "get", counter: this.counter, worker: "do-provider", }); }
return new Response("SharedCounter DO is running!", { status: 200 }); }}
export default { async fetch(request: Request): Promise<Response> { return new Response("DO Provider Worker is running!"); },};
Client Worker Implementation
Section titled “Client Worker Implementation”The client Worker can access the shared Durable Object without implementing the Durable Object class:
import { env } from "cloudflare:workers";
export default { async fetch(request: Request): Promise<Response> { const url = new URL(request.url);
if (url.pathname === "/increment") { try { // Access the Durable Object defined in another worker const id = env.SHARED_COUNTER.idFromName("global-counter"); const stub = env.SHARED_COUNTER.get(id); const response = await stub.fetch( new Request("https://example.com/increment") ); const data = await response.json();
return Response.json({ success: true, clientWorker: "client-worker", result: data, crossScriptWorking: true, }); } catch (error) { return Response.json( { error: error.message, crossScriptWorking: false, }, { status: 500 } ); } }
if (url.pathname === "/get") { try { // Get the current counter value const id = env.SHARED_COUNTER.idFromName("global-counter"); const stub = env.SHARED_COUNTER.get(id); const response = await stub.fetch( new Request("https://example.com/get") ); const data = await response.json();
return Response.json({ success: true, clientWorker: "client-worker", result: data, }); } catch (error) { return Response.json( { error: error.message, }, { status: 500 } ); } }
return new Response("Client Worker is running!"); },};