Laravel-Inspired Patterns
Which Laravel patterns Roost adopts, what it deliberately changes, and the developer-experience philosophy behind both choices.
Why Draw From Laravel at All
Laravel earned its reputation not by being the most technically innovative PHP framework, but by being the most enjoyable one. It made opinionated choices — service providers, Eloquent's Active Record, artisan generators, facades — and it made them consistently. A developer who learned one part of Laravel could predict how every other part worked. Roost borrows this philosophy: strong conventions reduce the surface area of decisions developers have to make, and consistent patterns mean the framework's behavior is learnable rather than just discoverable through source code.
What Roost Adopts Directly
Service providers and the IoC container are the most direct inheritance.
The two-phase register()/boot() lifecycle, the singleton/transient
distinction, and the application bootstrap model all map closely to Laravel's. The semantics
are the same even though the implementation is TypeScript and the runtime is a V8 isolate
rather than a PHP-FPM process.
Active Record via the ORM follows Eloquent's style: a Model
class with static methods for querying (User.find(), User.where())
and instance methods for mutation (user.save(), user.delete()).
Model hooks (creating, created, updating) mirror
Eloquent's observer-like model events. Soft deletes, pagination, and eager loading are
first-class features for the same reasons they are in Eloquent: they are universally needed
and inconsistently implemented when left to application code.
Middleware as a pipeline follows Laravel's HTTP middleware contract. Each
middleware receives the request and a next handler, can modify the request
before passing it on, and can modify the response on the way back. The composability is the
same; the implementation uses the Web Platform Request/Response
types instead of Symfony's HTTP Foundation.
Artisan-style code generation is the model for @roostjs/cli.
The philosophy — generate a file once, own it forever — means the generated code is not
a locked-down black box but a starting point you modify freely.
What Roost Deliberately Changes
No facades. Laravel facades provide static-looking access to services
that are actually resolved from the container at call time. In PHP, this works cleanly
because PHP's __callStatic allows runtime method dispatch. In TypeScript,
static-looking APIs are just static methods — there is no equivalent mechanism. More
importantly, facades make dependency tracing harder: you cannot easily see from a function
signature what it depends on. Roost uses explicit container resolution and constructor
injection instead.
TypeScript-first, not configuration arrays. Laravel uses PHP arrays extensively — for migration definitions, model fillables, validation rules. Roost uses TypeScript classes and interfaces. A migration is a TypeScript file with typed up/down methods. Schema definitions use a fluent builder with type inference. This trades the brevity of array syntax for IDE support, refactoring safety, and compile-time error detection.
No magic property access via __get. Eloquent models in PHP
expose attributes as dynamic properties through PHP's __get magic. Roost's ORM
uses a Proxy to forward property access to model.attributes, but
the type system does not reflect this at the model class level. Typed attribute access is
an area where the TypeScript/JavaScript model diverges from PHP's, and Roost does not
try to paper over that gap with clever generics — it accepts the trade-off.
The DX Philosophy
"The Laravel of Cloudflare Workers" is not a marketing slogan — it is a constraint. It means that when there is a choice between two technically equivalent approaches, Roost chooses the one that feels more like Laravel. Convention over configuration. Generators over boilerplate. Explicit dependency graphs over hidden singletons. And when the Workers runtime makes a Laravel-identical approach impossible, Roost finds the closest equivalent that respects the runtime's actual constraints rather than pretending those constraints do not exist.