Skip to content

Client-side DO quickstart

  1. Define a Durable Object and its RPC type

    import { DurableObject } from 'cloudflare:workers';
    import {
    createRpcServer,
    type RpcFromTarget,
    } from '@repo/client-side-do';
    type Todo = { id: string; title: string; completed: boolean };
    const methods = ['listTodos', 'createTodo', 'setCompleted'] as const;
    export class TodoDO extends DurableObject<Env> {
    private readonly rpc = createRpcServer(this, methods);
    async listTodos(): Promise<Todo[]> {
    return this.ctx.storage.list<Todo>().then((items) => [...items.values()]);
    }
    async createTodo(todo: Todo): Promise<Todo> {
    await this.ctx.storage.put(todo.id, todo);
    return todo;
    }
    async setCompleted(id: string, completed: boolean): Promise<Todo> {
    const todo = await this.ctx.storage.get<Todo>(id);
    if (!todo) throw new Error('Todo not found');
    const updated = { ...todo, completed };
    await this.ctx.storage.put(id, updated);
    return updated;
    }
    fetch(request: Request) {
    return this.rpc.fetch(request);
    }
    webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
    return this.rpc.handleMessage(ws, message);
    }
    }
    export type TodoRpc = Pick<
    RpcFromTarget<InstanceType<typeof TodoDO>, 'fetch'>,
    (typeof methods)[number]
    >;
  2. Route an upgrade request to the object

    Your Worker route should resolve an object ID, get its stub, and forward the WebSocket upgrade request to stub.fetch(request).

  3. Create the browser transport

    import { createDOTransport } from '@repo/client-side-do/client';
    import type { TodoRpc } from './shared-rpc';
    const todos = createDOTransport<TodoRpc>({
    baseUrl: (id) => `wss://api.example.com/todos/${id}`,
    });
    const client = todos.client('team-42');
    const snapshot = await client.listTodos();
  4. Subscribe to changes

    const session = todos.subscriptions('team-42', {
    createTodo: ({ result }) => addToLocalState(result),
    setCompleted: ({ result }) => updateLocalState(result),
    });
    await session.client.createTodo({
    id: crypto.randomUUID(),
    title: 'Ship it',
    completed: false,
    });
    session.unsubscribe();

The caller receives a response. Other subscribed sockets receive a method event with the same arguments and result.