#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.

VerbPathActionName
GET/usersindexusers
POST/userscreateusers
GET/users/newnewnew-user
GET/users/:idshowuser
GET/users/:id/editeditedit-user
PATCH / PUT/users/:idupdateuser
DELETE/users/:iddestroyuser
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';
}
Flatten when it gets deep. Plural and singular resources nest either way, and nesting can go more than one level, though more than one level deep is usually a sign the routes want flattening with 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.

keayl routes
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