One file maps every request.
Routes are declared in config/routes.raku with a declarative DSL. A
routes block builds a router that maps an incoming method and path to a
controller action or an inline handler, and generates the URL helpers that point back at them.
#verb helpers
get, post, put, patch,
delete, and options each declare a route for that method. The target
is a 'controller#action' string or an inline Callable. A GET
route also answers HEAD. root maps GET /, and
match registers one path for several verbs with via.
use MVC::Keayl::Routing;
routes {
root to => 'home#index';
get '/users', to => 'users#index';
post '/users', to => 'users#create';
# inline callable
get '/ping', to => sub { 'pong' };
match '/search', to => 'search#run',
via => <get post>;
match '/health', to => 'health#show',
via => 'all';
match '/any', to => 'catch#all', via => *;
}
At boot the application loads the file with load-routes('config/routes.raku'),
which evaluates it and returns the router its routes block built.
draw is an alias for routes.
#path patterns
A path can carry dynamic segments, a glob, optional groups, a format, and per-segment
constraints. The matched values become params on recognition. A :segment matches a
single segment, stopping at a / or a .; a *glob matches
everything that remains; anything inside (...) is optional.
# one segment
get '/users/:id', to => 'users#show';
# the rest, slashes included
get '/files/*path', to => 'files#serve';
# optional group
get '/users(/:id)', to => 'users#index';
# peel off an extension
get '/users/:id(.:format)', to => 'users#show';
# /users/abc falls through
get '/users/:id', to => 'users#show',
constraints => { id => /^\d+$/ },
defaults => { format => 'html' };
format => True appends an optional (.:format) without writing it
out. defaults supplies values for absent params, and constraints
restricts a segment to a pattern. A request whose segment fails the constraint falls through to
the next route.
#resources
resources 'users' declares the seven REST routes in one call. only
and except choose which to generate, member and
collection blocks add routes that act on a record or on the set, and a singular
resource drops the index and the :id.
| Verb | Path | Action | Name |
|---|---|---|---|
| GET | /users | index | users |
| POST | /users | create | users |
| GET | /users/new | new | new-user |
| GET | /users/:id | show | user |
| GET | /users/:id/edit | edit | edit-user |
| PATCH / PUT | /users/:id | update | user |
| DELETE | /users/:id | destroy | user |
resources 'users', 'posts';
resources 'users', :only<index show>;
resources 'photos', :except<destroy>;
resources 'photos', {
member {
# GET /photos/:id/preview
get 'preview', to => 'photos#preview';
}
collection {
# GET /photos/search
get 'search', to => 'photos#search';
}
}
# singular: no index, no :id
resource 'profile';
Resource options rename the pieces: path overrides the URL segment, as
the helper base, controller the target, module prefixes it,
param renames the member key, and path-names renames the
new and edit segments.
#nesting
Resources nest inside a resource block. A nested resource is scoped under the parent member, and
its key is named after the parent. shallow keeps the collection routes nested but
lifts the member routes to the top level, so member URLs stay short.
resources 'magazines', {
# /magazines/:magazine_id/ads/:id
resources 'ads';
}
resources 'magazines', :shallow, {
# collection nested, member at /ads/:id
resources 'ads';
}
shallow.
#namespaces and scopes
namespace prefixes the path, the controller module, and the helper name all at
once. scope controls each independently, controller sets the controller
for the routes inside, and an optional scope segment written in parentheses suits an i18n locale
prefix.
namespace 'admin', {
# /admin/users => admin/users, named admin-users
resources 'users';
}
scope(path => 'api',
module => 'v1',
as => 'api', {
# /api/ping => v1/ping#show
get '/ping', to => 'ping#show', as => 'ping';
});
scope('(:locale)', {
# matches /about and /en/about
get '/about', to => 'pages#about';
});
#concerns and constraints
A concern is a reusable block of routes: define it once with concern, then mix it
into resources with the concerns option or call. A constraints block
restricts the routes inside it, keyed on path params or request attributes like
subdomain, host, and format.
concern 'commentable', { resources 'comments' };
resources 'posts', concerns => 'commentable';
resources 'photos', { concerns 'commentable' };
constraints(:subdomain<api>, {
# only when the subdomain is api
get '/data', to => 'data#index';
});
constraints(
-> %context {
%context<host>.ends-with('.internal')
}, {
get '/admin', to => 'admin#index';
});
#redirects and mounting
A route can redirect instead of dispatching. redirect takes a string or a block
that computes the location from the params, with an optional status. mount attaches
a sub-app at a path, capturing the remainder as mounted_path.
get '/stories', to => redirect('/articles');
get '/movies/:id', to => redirect(
-> %params { '/films/' ~ %params<id> },
status => 302);
# matches /legacy and /legacy/...
mount $rack-app, at => '/legacy';
#URL helpers
MVC::Keayl::Routing::UrlHelpers generates paths and URLs from named routes.
path-for fills the segments and turns leftover params into a sorted query string;
url-for builds an absolute URL from default-url-options. Per-route
name-path and name-url helpers resolve through FALLBACK.
my $helpers = MVC::Keayl::Routing::UrlHelpers.new(
:$router,
:default-url-options({
host => 'example.com'
}));
# /users/5
$helpers.path-for('user', 5);
# /users/5?page=2
$helpers.path-for('user', 5, page => 2);
# /users/5#comments
$helpers.path-for('user', 5,
anchor => 'comments');
# /users/5
$helpers.user-path(5);
# http://example.com/users
$helpers.users-url;
# /posts/7
$helpers.polymorphic-path($persisted-post);
# /posts
$helpers.polymorphic-path($new-post);
The router answers recognize($method, $path) with a match or an undefined match,
and recognition-status distinguishes a hit from a wrong method and from an unknown
path. route-table returns the name, verbs, pattern, and target of every route,
which keayl routes prints.
Name Verb Path Target
root GET / home#index
articles GET /articles articles#index
article GET /articles/:id articles#show
new-article GET /articles/new articles#new
article PATCH /articles/:id articles#update
article DELETE /articles/:id articles#destroy