Routing
Route has two phases, strictly separated: registration builds a table of
method/pattern/action entries; dispatch() matches the current request against that
table exactly once. Every method returns self, so ->name(),
->middleware(), and ->where() chain off the registration call.
Registering routes
use Monad\Clarity\Services\Response;
use Monad\Clarity\Services\Route;
Route::get('/hello', fn () => Response::text('Hello, Monad.'));
Route::post('/hello', fn () => Response::json(['received' => true]));
Route::put('/hello/{id}', fn (string $id) => Response::json(['id' => $id]));
Route::patch(/* ... */);
Route::delete(/* ... */);
Route::options(/* ... */);
An action is a closure or a [ControllerClass::class, 'method'] pair — controller
actions are plain static methods, directly callable with no resolving step. See
Quickstart for a worked controller example.
Named parameters and constraints
{id} matches any non-slash segment. Typed shorthands narrow the pattern:
{id:int} (\d+), {slug:alpha} ([A-Za-z]+), and
{id:uuid}. A trailing ? makes a parameter optional —
{id?} or {id:int?}. ->where() overrides the pattern for a
specific parameter with your own regex:
Route::get('/posts/{slug:alpha}', fn (string $slug) => Response::text($slug));
Route::get('/posts/{id}', fn (?string $id) => Response::text($id ?? 'all'))->where('id', '\d{4}');
Groups
Route::group() shares a URI prefix and middleware stack across the routes registered
inside its callback. Groups nest — a nested group's prefix and middleware both accumulate onto its
parent's:
Route::group(['prefix' => 'admin', 'middleware' => 'App\Middlewares\Authentication'], function () {
Route::get('/dashboard', fn () => Response::text('Admin dashboard'));
Route::group(['prefix' => 'reports'], function () {
Route::get('/monthly', fn () => Response::text('Monthly report'));
// Registers GET /admin/reports/monthly, with Authentication applied.
});
});
Named routes
Route::get('/hello/{name}', [GreetingController::class, 'show'])->name('greeting.show');
404 vs 405
dispatch() sees every registered route before deciding, so it can tell "no route
matched this path" (404) apart from "a route matched this path, but not this HTTP method" (405) —
something a router that matches and calls immediately on the first hit structurally cannot do,
since it never examines the routes it didn't try. If nothing matches the path at all and
Route::fallback() is registered, that runs instead of a bare 404:
Route::fallback(fn () => Response::text('Not Found', 404));
Next steps
- Middleware Pipeline — how
->middleware()and group middleware actually run, in what order. - Request/Response Lifecycle — how a matched route's action turns into the response that gets sent.