Quickstart (Composable Routing)

Updated Oct 15, 2026
import falcon.asgi
from falcon_pachinko import WebSocketResource, WebSocketRouter, handles_message


class ChatResource(WebSocketResource):
    async def on_connect(self, req, ws, room: str) -> bool:
        self.state["room"] = room
        return True  # continue to message handling

    @handles_message("chat.message")
    async def handle_message(self, ws, payload):
        await ws.send_media({"type": "echo", "text": payload.text})


app = falcon.asgi.App()
router = WebSocketRouter(receive_loop=True)
router.add_route("/chat/{room}", ChatResource)
router.attach(app, "/ws")
  • attach() mounts the router at /ws and registers a Falcon catch-all route for descendant paths. It also enables pass-through TEXT and BINARY input for every WebSocket route on the app, while preserving the configured outgoing serializers. It warns if custom media handlers will be replaced.
  • Each connection receives a fresh resource instance and a shared state proxy scoped to that connection.
  • The example opts into a router-owned receive-and-dispatch session with receive_loop=True. The default is False, which returns after acceptance and leaves receives and cleanup to the application.

The `add_route` signature

def add_route(
    self,
    path: str,
    resource: type[WebSocketResource] | cabc.Callable[..., WebSocketResource],
    *init_args: object,
    name: str | None = None,
    **init_kwargs: object,
) -> None: ...
  • *init_args and **init_kwargs are stored and passed to resource when a connection instantiates it, alongside the path parameters. Example passing both a positional and a keyword initializer argument:
  router.add_route("/rooms/{room}", ChatResource, "lobby", history_size=100)
  • name is reserved for naming the route itself and is never forwarded to the resource initializer.
  • resource accepts any callable returning a WebSocketResource, not only a class, so a functools.partial factory covers a resource whose own initializer needs a parameter called name:
  router.add_route("/p", functools.partial(MyResource, name="x"), name="route")

Route template syntax

Route templates match their literal text exactly. Regular-expression metacharacters such as ., +, brackets, and parentheses have no special meaning in a route. A parameter written as {name} captures one or more non-/ characters; parameter names must be valid Python identifiers. A path segment may contain one parameter or an adjacent pair at its end. Other multiple-parameter layouts are rejected to bound regex matching work.

Braces are reserved for parameters. Empty, unmatched, nested, or doubled braces, invalid names, duplicate parameter names, and ambiguous multiple- parameter layouts raise ValueError during route or subroute registration. A conflict between a mount-prefix parameter and a route parameter raises ValueError when the router is mounted. Full routes and nested subroutes are checked in registration order, so the first matching registration selects the resource and its hooks.