Register and mount
host.durableObject, per-DO routes, and mount() on the Worker front door.
Register a Durable Object class on the host, add routes on the returned handle, then mount that DO at a URL prefix on the Worker front door.
Register routes
Section titled “Register routes”import { FlareHost, FlareResponse } from "@flare-ts/core";import { cf, DurableState, FlareDurableObject } from "@flare-ts/core/cloudflare";import { str } from "@flare-ts/lib/schema";
class Room extends FlareDurableObject { public static override deps = [DurableState] as const;}
const host = new FlareHost(cf);const room = host.durableObject(Room, { binding: "ROOM" });
room.http.get( "/meta", { inject: { ds: DurableState } }, (_ctx, scope) => new FlareResponse(200, { id: scope.ds.id.toString() }),);
room.ws.route("/live/:topic", { params: { topic: str } }) .message((ws, scope) => ws.send(scope.input.message.raw));
room.mount("/rooms/:name");binding names the env key Wrangler uses (defaults to the class name). HTTP and WebSocket traffic under /rooms/... resolves to a DO instance named by the trailing :name segment.
mount() rules
Section titled “mount() rules”- Path must start with
/and be non-empty - No wildcard segments in the mount path (the mount adds its own routing)
- A param-trailing mount (
/rooms/:name) takes the instance name from the trailing parameter; a literal-trailing mount (/api/me) needsresolve(...)registered on the handle beforebuild(), or the build fails withMOUNT_REQUIRES_RESOLVE - A mount’s subtree is owned exclusively by the DO: overlap with any front-door route, group prefix, or other mount fails
build()withMOUNT_ROUTE_CONFLICT
Do not call the raw DO binding’s fetch() from application code: nothing strips a client-forged state envelope on that path. Route DO traffic through .mount() or durable(). durable(namespace, name).fetch() is a state-free raw tunnel by design (it strips the reserved framework state headers); durable(...).forward(ctx, Class) is the state-carrying call.
resolve() for literal-trailing mounts
Section titled “resolve() for literal-trailing mounts”A literal-trailing mount (/api/me) has no :name segment to name the instance, so register resolve(...) on the handle. Two overloads: resolve(handler) with no injected deps, or resolve({ inject }, handler) for typed DI. The return value decides what happens:
- a
stringis the DO instance name (the request forwards to it); - a
FlareResponseshort-circuits (it is returned; no DO is entered); - a throw goes through the normal error pipeline.
resolve runs in the Worker front-door context, so it can inject front-door services (auth, session, Bindings). A DurableState-dependent service fails host.build().
import { FlareResponse } from "@flare-ts/core";
room.mount("/api/me"); // literal-trailing: needs resolve()
room.resolve({ inject: { sessions: Sessions } }, (ctx, scope) => { const user = scope.sessions.current(ctx); if (!user) return new FlareResponse(401); return user.id; // the DO instance name});wrangler.toml
Section titled “wrangler.toml”[[durable_objects.bindings]]name = "ROOM"class_name = "Room"
[[migrations]]tag = "v1"new_classes = ["Room"]Export the DO class from the same module as export default app.export().
Co-locate registration
Section titled “Co-locate registration”host.durableObject(Class, builder) (or (Class, opts, builder)) invokes the builder immediately with the same handle, so HTTP, WebSocket, mount, and resolve can be registered in one co-located block.
import { FlareHost, FlareResponse } from "@flare-ts/core";import { cf, FlareDurableObject } from "@flare-ts/core/cloudflare";
class Room extends FlareDurableObject { public static override deps = [];}
const host = new FlareHost(cf);
host.durableObject(Room, (room) => { room.http.get("/ping", () => new FlareResponse(200, { ok: true })); room.ws.route("/live"); room.mount("/rooms/:name");});The handle returned from host.durableObject() is the same in all forms.