Application Architecture
How a Roost application is structured, what happens at boot time, and how a request travels from the Cloudflare edge to a response.
A Single Entry Point at the Edge
Every Roost application is a Cloudflare Worker — a V8 isolate that receives HTTP requests and returns HTTP responses. There is no load balancer to configure, no Nginx to tune, and no server process to keep alive. The Worker is both the web server and the application framework running inside it.
The Application class from @roostjs/core is the heart of this setup. It holds
the service container, owns the list of registered service providers, and exposes a single
handle(request) method that the Worker's fetch handler calls for every
incoming request. All of Roost's bootstrap logic flows through this one object.
Boot Sequence
Roost boots lazily. When the first request arrives, Application.handle() notices the
application has not yet booted and calls Application.boot() automatically. Boot runs in
two strict phases. First, every registered service provider's register() method is called
in order — this is where bindings are added to the container. Then, every provider's optional
boot() method is called. The separation matters: a provider's boot() can
safely resolve things registered by a different provider, because all registrations are finished
before any booting begins.
Once booted, the application stays booted for the lifetime of the V8 isolate. On Cloudflare Workers, isolates are reused across many requests, so the boot cost is paid once and amortized. Subsequent requests skip straight to the middleware pipeline.
The Middleware Pipeline
After boot, each request enters the middleware pipeline. Roost builds a new scoped container for every request — a child container that inherits all singletons from the application container but can register its own request-scoped bindings without polluting the shared state. The pipeline hands this scoped container to each middleware in sequence.
Middleware is a chain of responsibility: each piece calls next(request) to pass control
to the next middleware in the chain, or returns a Response early to short-circuit. This
lets authentication middleware reject requests before they reach route handlers, and response
middleware modify the response on the way back out. The final handler in the chain — called the
"destination" — is where routing logic lives.
TanStack Start and the Context Bridge
When Roost is used with @roostjs/start, TanStack Start handles routing and SSR. The
Roost application runs as middleware inside TanStack Start's request pipeline, not as a standalone
server. This means TanStack Start's file-based router resolves the route, but Roost's middleware
pipeline runs first — authenticating the request, resolving the organization, and making services
available via the scoped container.
Server functions (via roostFn) capture the request-scoped container and make it
available to TanStack Start's data-loading layer. This bridges the server-side Roost context
into the React component tree without passing dependencies through props or relying on module-level
globals.