Routing
Routes are declared with a keyword per method and compiled into a frozen
table at to_app. Dispatch is a hash lookup for literal paths and a
short bucket scan for parameterised ones.
Routes
get '/books/:id' => 'Web::Book#view';
any '/ping' => sub { my ($c) = @_; $c->text('pong') };
post '/books' => 'Web::Book#create';
get, post, put, patch, del and any each take a path and a
target. The target is a coderef, or a 'Controller#method' string
resolved against MyApp::Controller:: at boot - typos croak before the
app serves.
Captures
:name captures one path segment, *name captures the rest:
get '/books/:id' => sub { $_[0]->text('book ' . $_[0]->param('id')) };
get '/files/*path' => sub { $_[0]->text($_[0]->param('path')) };
Captures are available as $c->param($name), alongside query and form
parameters - see
the context's parameter helpers.
Guard scopes
under opens a scope guarded by a coderef. Guards receive the context;
a reference return short-circuits the request, anything else continues.
Scopes nest, and each nested scope inherits its parents' guards:
my $admin = under '/admin' => sub {
my ($c) = @_;
return $c->redirect('/') unless $c->session->{is_admin};
return; # fall through: carry on
};
$admin->get('/books' => 'Admin::Book#list');
$admin->post('/books' => 'Admin::Book#create');
At request time the guard chain is a frozen array walk - no dispatch logic runs to find them.
Hooks
hook before_dispatch => sub { my ($c) = @_; ...; return };
hook after_dispatch => sub { my ($c, $resp) = @_; ... };
before_dispatch runs after routing and before guards; a reference
return short-circuits. after_dispatch sees the finalized PSGI triplet
and may mutate it or return a replacement.
Errors
on_error sub {
my ($c, $err) = @_;
return $c->json({ oops => 1 }, 500);
};
Runs when a guard or handler dies; a reference return becomes the
response, otherwise the default 500 {"errors":[...]} is served.
Mounts
Whole applications mount under a prefix, longest prefix first:
static 'https://fal.aihtbprolstatic-tp.hcv9jop5ns4r.cn' => 'root/static';
markdown '/docs' => 'docs', title => 'MyApp Guide';
mount '/legacy' => $psgi_app;
A literal route still wins over a mount; parameterised routes lose to a
mount prefix. See OpenAPI for the api mount and
the markdown-site recipe for markdown.
Special route kinds
websocket and sse routes sit in the same table, under the same
scopes and guards - a guard can reject a client with an ordinary HTTP
response before any upgrade happens. See
WebSockets and SSE.