> ## Documentation Index
> Fetch the complete documentation index at: https://wemstudios.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Durable Objects in Blaze: Stateful Workers at the Edge

> Access Cloudflare Durable Objects from Blaze via req.env. Forward requests to named DO instances for stateful logic like chat rooms and presence.

Durable Objects are single-instance, stateful Workers that Cloudflare routes all traffic for a given ID to the same physical server — making them ideal for chat rooms, presence systems, collaborative editing, game sessions, and any use case where you need globally consistent state without an external database. Blaze gives you access to your Durable Object namespaces via `req.env` using the same type-safe pattern as every other binding, so forwarding a request to a specific DO instance is just a few lines of code.

## Configure the binding

<Steps>
  <Step title="Declare the Durable Object in wrangler.toml">
    ```toml wrangler.toml theme={null}
    [[durable_objects.bindings]]
    name       = "ROOMS"
    class_name = "ChatRoom"

    # The migration tells Cloudflare to create the DO class
    [[migrations]]
    tag         = "v1"
    new_classes = ["ChatRoom"]
    ```
  </Step>

  <Step title="Define the Env type">
    ```typescript src/types/env.ts theme={null}
    export type Env = {
      ROOMS: DurableObjectNamespace;
      // ...other bindings
    };
    ```
  </Step>

  <Step title="Pass Env to createApp">
    ```typescript src/index.ts theme={null}
    import { createApp } from "blaze";
    import type { Env } from "./types/env";

    const app = createApp<Env>();
    // req.env.ROOMS is fully typed as DurableObjectNamespace in every handler
    ```
  </Step>
</Steps>

<Note>
  The Durable Object class (`ChatRoom` in the example above) must be **defined
  and exported separately** in your Worker entry point. The class must extend
  `DurableObject` and implement a `fetch(request)` method. Blaze handles the
  HTTP routing layer; your DO class handles the stateful logic.

  ```typescript src/durable-objects/chat-room.ts theme={null}
  export class ChatRoom {
    state: DurableObjectState;

    constructor(state: DurableObjectState) {
      this.state = state;
    }

    async fetch(request: Request): Promise<Response> {
      // Handle WebSocket upgrades, messages, presence, etc.
      return new Response("ChatRoom ready");
    }
  }
  ```

  Then re-export it from your entry point:

  ```typescript src/index.ts theme={null}
  export { ChatRoom } from "./durable-objects/chat-room";
  export default { fetch: app.fetch };
  ```
</Note>

## Getting a DO instance

Every Durable Object instance is identified by a `DurableObjectId`. Derive an ID in one of two ways:

| Method                | When to use                                                                                           |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `idFromName(name)`    | Stable, human-readable key (e.g. room slug, user ID). Same name always resolves to the same instance. |
| `idFromString(hexId)` | Re-hydrate a previously generated ID stored in a database or passed as a URL parameter.               |

```typescript theme={null}
app.get("/rooms/:id", async (req, res) => {
  // idFromName — always routes to the same instance for a given room name
  const doId = req.env.ROOMS.idFromName(req.params.id);

  // idFromString — reconstruct an ID you stored earlier
  // const doId = req.env.ROOMS.idFromString(req.params.id);

  const stub = req.env.ROOMS.get(doId);

  // stub is a DurableObjectStub — call fetch() to communicate with the instance
  const response = await stub.fetch(new Request("https://do-internal/info"));
  const info = await response.json();

  res.json(info);
});
```

## Forwarding requests to a DO

The most common pattern is to forward the entire incoming request — including a WebSocket upgrade — directly to the Durable Object. The DO handles the upgrade and owns the persistent connection state.

```typescript theme={null}
app.get("/rooms/:id/ws", async (req, res) => {
  // Derive a stable ID from the room name so all clients for the
  // same room always land on the same DO instance
  const doId = req.env.ROOMS.idFromName(req.params.id);
  const stub  = req.env.ROOMS.get(doId);

  // Forward the raw WebSocket upgrade request to the DO
  const response = await stub.fetch(req.raw);

  // Pass the DO's response (101 Switching Protocols) back to the client
  res.raw(response);
});
```

You can forward any request type — REST calls, streaming, RPC — not just WebSocket upgrades:

```typescript theme={null}
app.post("/rooms/:id/message", async (req, res) => {
  const doId = req.env.ROOMS.idFromName(req.params.id);
  const stub  = req.env.ROOMS.get(doId);

  const body = await req.json<{ text: string; author: string }>();

  const doResponse = await stub.fetch(
    new Request("https://do-internal/message", {
      method:  "POST",
      headers: { "Content-Type": "application/json" },
      body:    JSON.stringify(body),
    })
  );

  const result = await doResponse.json();
  res.status(doResponse.status).json(result);
});
```

## Named instances

`idFromName()` is the building block for stable, long-lived DO instances tied to application entities. Use a consistent naming scheme so every part of your application resolves to the same instance for a given entity.

```typescript theme={null}
// One DO instance per user — coordinates their real-time presence
app.get("/users/:userId/presence", async (req, res) => {
  const doId = req.env.ROOMS.idFromName(`user:${req.params.userId}`);
  const stub  = req.env.ROOMS.get(doId);

  const presence = await stub.fetch(
    new Request("https://do-internal/presence")
  );

  res.send(await presence.text());
});

// One DO instance per org × document — coordinates collaborative edits
app.get("/docs/:docId/collab", async (req, res) => {
  const doId = req.env.ROOMS.idFromName(`doc:${req.params.docId}`);
  const stub  = req.env.ROOMS.get(doId);

  const response = await stub.fetch(req.raw);
  res.raw(response);
});
```

<Note>
  Named Durable Objects created with `idFromName()` are permanent — they are
  not garbage-collected until you explicitly delete them via the Cloudflare API
  or Wrangler. Design your naming scheme carefully; a new DO instance is
  provisioned the first time an ID is accessed, and storage costs accrue from
  that point forward.
</Note>
