Mojolic...Routing(3)
๐ Mojolicious::Guides::Routing โ Routing requests โจ
Okay bestie, this is Mojolic...Routing(3) from the User Contributed Perl Documentation. It's your simple, fun intro to the Mojolicious router and all the concepts underneath it, and we're in part 1 of 4, so don't go anywhere yet ๐ซถ
๐ NAME ๐
Mojolicious::Guides::Routing โ Routing requests
โจ OVERVIEW โจ
This doc is a simple and fun introduction to the Mojolicious router and its underlying concepts.
๐ง CONCEPTS ๐ง
The essentials every Mojolicious developer should know, no cap.
๐ณ๏ธ Dispatcher ๐ณ๏ธ
Every web framework has a tiny black box at its core: it connects incoming requests to the code that generates the right response.
GET /user/show/1 -> $c->render(text => 'Daniel');
That black box is usually called the dispatcher. People implement dispatchers in lots of different ways, but almost all of them do the same basic thing: map the path part of the request URL to some kind of response generator.
/user/show/2 -> $c->render(text => 'Isabell');
/user/show/3 -> $c->render(text => 'Sara');
/user/show/4 -> $c->render(text => 'Stefan');
/user/show/5 -> $c->render(text => 'Fynn');
You could hardcode every single one of those connections as a static thing, but that's lowkey inefficient. That's why regular expressions are the usual move for making dispatch more dynamic.
qr!/user/show/(\d+)! -> $c->render(text => $users{$1});
Modern dispatchers have basically all of HTTP at their disposal, so they can look at way more than just the request path โ things like the request method and headers such as Host, User-Agent and Accept, periodt.
GET /user/show/23 HTTP/1.1
Host: mojolicious.org
User-Agent: Mojolicious (Perl)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
๐ฃ๏ธ Routes ๐ฃ๏ธ
Regexes are powerful, sure, but they're also kinda unpleasant to look at and honestly overkill for ordinary path matching.
qr!/user/admin/(\d+)! -> $c->render(text => $users{$1});
Enter routes: they were literally designed from the ground up to represent paths with placeholders.
/user/admin/:id -> $c->render(text => $users{$id});
The only difference between a static path and the route above is the :id placeholder. You can put one or more placeholders anywhere in the route.
/user/:role/:id
A fundamental concept of the Mojolicious router is that extracted placeholder values get turned into a hash.
/user/admin/23 -> /user/:role/:id -> {role => 'admin', id => 23}
That hash is basically the center of every Mojolicious application โ you'll learn more about it later. Internally, routes get compiled to regular expressions, so with a little experience you get the best of both worlds.
/user/admin/:id -> qr/(?-xism:^\/user\/admin\/([^\/.]+))/
A trailing slash in the path is always optional.
/user/admin/23/ -> /user/:role/:id -> {role => 'admin', id => 23}
๐ Reversibility ๐
One more huge advantage routes have over regular expressions: they're easily reversible. Extracted placeholders can be turned back into a path at any time.
/sebastian -> /:name -> {name => 'sebastian'}
{name => 'sebastian'} -> /:name -> /sebastian
Every placeholder has a name, even if it's just an empty string.
๐ค Standard placeholders ๐ค
Standard placeholders are the simplest kind. They use a colon prefix and match all characters except / and ., similar to the regular expression ([^/.]+).
/hello -> /:name/hello -> undef
/sebastian/23/hello -> /:name/hello -> undef
/sebastian.23/hello -> /:name/hello -> undef
/sebastian/hello -> /:name/hello -> {name => 'sebastian'}
/sebastian23/hello -> /:name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /:name/hello -> {name => 'sebastian 23'}
All placeholders can be surrounded by < and > to separate them from the surrounding text.
/hello -> /<:name>hello -> undef
/sebastian/23hello -> /<:name>hello -> undef
/sebastian.23hello -> /<:name>hello -> undef
/sebastianhello -> /<:name>hello -> {name => 'sebastian'}
/sebastian23hello -> /<:name>hello -> {name => 'sebastian23'}
/sebastian 23hello -> /<:name>hello -> {name => 'sebastian 23'}
The colon prefix is optional for standard placeholders that are surrounded by < and >.
/iโฅmojolicious -> /<one>โฅ<two> -> {one => 'i', two => 'mojolicious'}
๐ Relaxed placeholders ๐
Relaxed placeholders are just like standard placeholders, but they use a hash prefix and match all characters except /, similar to the regular expression ([^/]+).
/hello -> /#name/hello -> undef
/sebastian/23/hello -> /#name/hello -> undef
/sebastian.23/hello -> /#name/hello -> {name => 'sebastian.23'}
/sebastian/hello -> /#name/hello -> {name => 'sebastian'}
/sebastian23/hello -> /#name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /#name/hello -> {name => 'sebastian 23'}
They can be especially useful for manually matching file names with extensions, rather than using format detection.
/music/song.mp3 -> /music/#filename -> {filename => 'song.mp3'}
๐ Wildcard placeholders ๐
Wildcard placeholders are just like the two types of placeholders above, but they use an asterisk prefix and match absolutely everything, including / and ., similar to the regular expression (.+).
/hello -> /*name/hello -> undef
/sebastian/23/hello -> /*name/hello -> {name => 'sebastian/23'}
/sebastian.23/hello -> /*name/hello -> {name => 'sebastian.23'}
/sebastian/hello -> /*name/hello -> {name => 'sebastian'}
/sebastian23/hello -> /*name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /*name/hello -> {name => 'sebastian 23'}
They can be useful for manually matching entire file paths.
/music/rock/song.mp3 -> /music/*filepath -> {filepath => 'rock/song.mp3'}
๐ธ BASICS ๐ธ
The most commonly used features every Mojolicious developer should know about.
๐ง Minimal route ๐ง
The attribute routes in Mojolicious contains a router you can use to generate route structures.
# Application
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
sub startup ($self) {
# Router
my $r = $self->routes;
# Route
$r->get('/welcome')->to(controller => 'foo', action => 'welcome');
}
1;
That minimal route above will load and instantiate the class MyApp::Controller::Foo and call its welcome method. Routes are usually configured in the startup method of the application class, but the router can be accessed from everywhere โ even at runtime.
# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller', -signatures;
# Action
sub welcome ($self) {
# Render response
$self->render(text => 'Hello there.');
}
1;
All routes match in the same order in which they were defined, and matching stops as soon as a suitable route has been found. So you can improve routing performance by declaring your most frequently accessed routes first. A routing cache will also be used automatically to handle sudden traffic spikes more gracefully. That's the whole vibe check: order matters, fr fr โจ
๐ฏ Routing destination ๐ฏ
After you start a new route with methods like get in Mojolicious::Routes::Route, you can also give it a destination in the form of a hash using the chained method to in Mojolicious::Routes::Route.
# /welcome -> {controller => 'foo', action => 'welcome'}
$r->get('/welcome')->to(controller => 'foo', action => 'welcome');
Now if the route matches an incoming request, it will use the content of this hash to try and find appropriate code to generate a response.
๐ฎ HTTP methods ๐ฎ
There are already shortcuts for the most common HTTP request methods like post in Mojolicious::Routes::Route, and for more control any in Mojolicious::Routes::Route accepts an optional array reference with arbitrary request methods as first argument.
# PUT /hello -> undef
# GET /hello -> {controller => 'foo', action => 'hello'}
$r->get('/hello')->to(controller => 'foo', action => 'hello');
# PUT /hello -> {controller => 'foo', action => 'hello'}
$r->put('/hello')->to(controller => 'foo', action => 'hello');
# POST /hello -> {controller => 'foo', action => 'hello'}
$r->post('/hello')->to(controller => 'foo', action => 'hello');
# GET|POST /bye -> {controller => 'foo', action => 'bye'}
$r->any(['GET', 'POST'] => '/bye')->to(controller => 'foo', action => 'bye');
# * /whatever -> {controller => 'foo', action => 'whatever'}
$r->any('/whatever')->to(controller => 'foo', action => 'whatever');
There is one small exception: HEAD requests are considered equal to GET, but content will not be sent with the response even if it is present.
# GET /test -> {controller => 'bar', action => 'test'}
# HEAD /test -> {controller => 'bar', action => 'test'}
$r->get('/test')->to(controller => 'bar', action => 'test');
You can also use the _method query parameter to override the request method. This can be very useful when submitting forms with browsers that only support GET and POST.
# PUT /stuff -> {controller => 'baz', action => 'stuff'}
# POST /stuff?_method=PUT -> {controller => 'baz', action => 'stuff'}
$r->put('/stuff')->to(controller => 'baz', action => 'stuff');
๐ IRIs ๐
IRIs are handled transparently, that means paths are guaranteed to be unescaped and decoded from bytes to characters.
# GET /โ (Unicode snowman) -> {controller => 'foo', action => 'snowman'}
$r->get('/โ')->to(controller => 'foo', action => 'snowman');
๐ Stash ๐
The generated hash of a matching route is actually the center of the whole Mojolicious request cycle. We call it the stash, and it persists until a response has been generated.
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
$r->get('/bye')->to(controller => 'foo', action => 'bye', mymessage => 'Bye');
There are a few stash values with special meaning, such as controller and action, but you can generally fill it with whatever data you need to generate a response. Once dispatched, the whole stash content can be changed at any time.
sub bye ($self) {
# Get message from stash
my $msg = $self->stash('mymessage');
# Change message in stash
$self->stash(mymessage => 'Welcome');
}
You can use defaults in Mojolicious to set default stash values that will be available everywhere in the application.
$app->defaults(mymessage => 'Howdy');
For a full list of reserved stash values see stash in Mojolicious::Controller.
๐ณ Nested routes ๐ณ
It is also possible to build tree structures from routes to remove repetitive code. A route with children can't match on its own though, only the actual endpoints of these nested routes can.
# /foo -> undef
# /foo/bar -> {controller => 'foo', action => 'bar'}
my $foo = $r->any('/foo')->to(controller => 'foo');
$foo->get('/bar')->to(action => 'bar');
ok so the stash just gets inherited from route to route, and whenever a newer value shows up it overrides the old one ๐ซถ lowkey that's the whole rule, no cap.
# /cats -> {controller => 'cats', action => 'index'}
# /cats/nyan -> {controller => 'cats', action => 'nyan'}
# /cats/lol -> {controller => 'cats', action => 'default'}
my $cats = $r->any('/cats')->to(controller => 'cats', action => 'default');
$cats->get('/')->to(action => 'index');
$cats->get('/nyan')->to(action => 'nyan');
$cats->get('/lol');
and bestie, if you set up a few common prefixes like this you also greatly improve the routing performance of apps with a ton of routes, because children are only even tried if the prefix matched first โจ that's the glow-up.
๐ Special stash values ๐
So when the dispatcher peeks in the stash and sees controller and action values chilling there, it will always try to turn them into a class and a method to dispatch to. The controller value gets converted from snake_case to CamelCase using camelize in Mojo::Util, and then it's appended to one or more namespaces. Those default to a controller namespace based on the application class (MyApp::Controller), plus the bare application class (MyApp), and the namespaces are searched in that order. The action value is not changed at all, so both values are case-sensitive ๐
# Application
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
sub startup ($self) {
# /bye -> MyApp::Controller::Foo->bye
$self->routes->get('/bye')->to(controller => 'foo', action => 'bye');
}
1;
# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller', -signatures;
# Action
sub bye ($self) {
# Render response
$self->render(text => 'Good bye.');
}
1;
Controller classes are literally perfect for organizing code in bigger projects ๐ There are more dispatch strategies out there, but because controllers are the most commonly used ones they also got a special shortcut in the form of controller#action, iykyk:
# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
$r->get('/bye')->to('foo#bye', mymessage => 'Bye');
During camelization, - characters get replaced with ::, and that's what lets you do multi-level controller hierarchies ๐ฆ
# / -> MyApp::Controller::Foo::Bar->hi
$r->get('/')->to('foo-bar#hi');
You can also just hand over the controller in CamelCase form instead of snake_case, no biggie:
# / -> MyApp::Controller::Foo::Bar->hi
$r->get('/')->to('Foo::Bar#hi');
โ ๏ธ For security reasons the dispatcher will always check if the controller is actually a subclass of Mojolicious::Controller or Mojo before dispatching to it. Like, it will vibe check you. No sneaking random classes past it ๐
๐ธ Namespaces ๐ธ
You can use the namespace stash value to change the namespace of a whole route with all its children. One line, whole squad updated:
# /bye -> MyApp::MyController::Foo::Bar->bye
$r->get('/bye')->to(namespace => 'MyApp::MyController', controller => 'Foo::Bar', action => 'bye');
The controller is always converted from snake_case to CamelCase with camelize in Mojo::Util, and then appended to this namespace.
# /bye -> MyApp::MyController::Foo::Bar->bye
$r->get('/bye')->to('foo-bar#bye', namespace => 'MyApp::MyController');
# /hey -> MyApp::MyController::Foo::Bar->hey
$r->get('/hey')->to('Foo::Bar#hey', namespace => 'MyApp::MyController');
And you can also change the default namespaces for every route in the app with the router attribute namespaces in Mojolicious::Routes, which usually defaults to a namespace based on the application class (MyApp::Controller), as well as the bare application class (MyApp) โญ
$r->namespaces(['MyApp::MyController']);
๐ Route to callback ๐
The cb stash value โ heads up, it won't be inherited by nested routes โ can be used to bypass controllers entirely and execute a callback instead. Slay.
$r->get('/bye')->to(cb => sub ($c) {
$c->render(text => 'Good bye.');
});
But just like in Mojolicious::Lite you can also just pass the callback directly, which usually looks so much better ๐
$r->get('/bye' => sub ($c) {
$c->render(text => 'Good bye.');
});
๐ Named routes ๐
Naming your routes lets you backreference them in a bunch of methods and helpers all over the framework, and honestly most of those internally rely on url_for in Mojolicious::Controller for it. Main character helper energy fr.
# /foo/marcus -> {controller => 'foo', action => 'bar', user => 'marcus'}
$r->get('/foo/:user')->to('foo#bar')->name('baz');
# Generate URL "/foo/marcus" for route "baz" (in previous request context)
my $url = $c->url_for('baz');
# Generate URL "/foo/jan" for route "baz"
my $url = $c->url_for('baz', user => 'jan');
# Generate URL "http://127.0.0.1:3000/foo/jan" for route "baz"
my $url = $c->url_for('baz', user => 'jan')->to_abs;
You can assign a name with name in Mojolicious::Routes::Route, or just let the router generate one automatically โ which would be equal to the route itself minus non-word characters. Custom names have higher precedence though, obviously ๐
# /foo/bar ("foobar")
$r->get('/foo/bar')->to('test#stuff');
# Generate URL "/foo/bar"
my $url = $c->url_for('foobar');
To refer to the current route you can use the reserved name current, or honestly no name at all:
# Generate URL for current route
my $url = $c->url_for('current');
my $url = $c->url_for;
And to check or get the name of the current route you've got the helper current_route in Mojolicious::Plugin::DefaultHelpers:
# Name for current route
my $name = $c->current_route;
# Check route name in code shared by multiple routes
$c->stash(button => 'green') if $c->current_route('login');
๐ฆ Optional placeholders ๐ฆ
Extracted placeholder values will simply redefine older stash values if they already exist. Old value found โ respectfully replaced, periodt.
# /bye -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
# /hey -> {controller => 'foo', action => 'bar', mymessage => 'hey'}
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');
And here's one more genuinely interesting effect: a placeholder automatically becomes optional if there's already a stash value of the same name present. It works similar to the regular expression ([^/.]+)? โจ
# / -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');
# /test/123 -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
# /test/bye/123 -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
$r->get('/test/:mymessage/123')->to('foo#bar', mymessage => 'hi');
And if two optional placeholders are only separated by a slash, that slash can become optional as well ๐ cute right?
๐ฅ Restrictive placeholders ๐ฅ
A very easy way to make placeholders more restrictive is alternatives โ you just make a list of possible values, which then work similar to the regular expression (bender|leela).
# /fry -> undef
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
# /leela -> {controller => 'foo', action => 'bar', name => 'leela'}
$r->get('/:name' => [name => ['bender', 'leela']])->to('foo#bar');
You can also adjust the regular expressions behind placeholders directly. Just make sure not to use ^ and $ or capturing groups (...), because placeholders become part of a larger regular expression internally. Non-capturing groups (?:...) are totally fine though ๐ซถ
# /23 -> {controller => 'foo', action => 'bar', number => 23}
# /test -> undef
$r->get('/:number' => [number => qr/\d+/])->to('foo#bar');
# /23 -> undef
# /test -> {controller => 'foo', action => 'bar', name => 'test'}
$r->get('/:name' => [name => qr/[a-zA-Z]+/])->to('foo#bar');
This way you get easily readable routes and the raw power of regular expressions. Both. At once. Iconic.
โญ Placeholder types โญ
And if you've got multiple routes all using restrictive placeholders, you can also turn them into placeholder types with add_type in Mojolicious::Routes. Reuse era ๐
# A type with alternatives
$r->add_type(futurama_name => ['bender', 'leela']);
# /fry -> undef
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
# /leela -> {controller => 'foo', action => 'bar', name => 'leela'}
$r->get('/<name:futurama_name>')->to('foo#bar');
Placeholder types work just like restrictive placeholders, they're simply reusable with the <placeholder:type> notation ๐
# A type adjusting the regular expression
$r->add_type(upper => qr/[A-Z]+/);
# /user/ROOT -> {controller => 'users', action => 'show', name => 'ROOT'}
# /user/root -> undef
# /user/23 -> undef
$r->get('/user/<name:upper>')->to('users#show');
Some types like num are used so commonly that they're just available by default, no setup needed:
# /article/12 -> {controller => 'article', action => 'show', id => 12}
# /article/test -> undef
$r->get('/article/<id:num>')->to('articles#show');
For a full list of available placeholder types see also TYPES in Mojolicious::Routes ๐
๐ Introspection ๐
The command Mojolicious::Command::routes can be used from the command line to list all available routes, together with names and underlying regular expressions. Built-in flex, honestly.
$ ./myapp.pl routes -v
/foo/:name .... POST fooname ^/foo/([^/.]+)/?(?:\.([^/]+))?$
/bar ..U. * bar ^/bar
+/baz ...W GET baz ^/baz/?(?:\.([^/]+))?$
/yada .... * yada ^/yada/?(?:\.([^/]+))?$
๐ง Under ๐ง
To share code with multiple nested routes you can use under in Mojolicious::Routes::Route โ because unlike normal nested routes, the routes generated with it have their own intermediate destination and result in additional dispatch cycles when they match ๐
# /foo -> undef
# /foo/bar -> {controller => 'foo', action => 'baz'}
# {controller => 'foo', action => 'bar'}
my $foo = $r->under('/foo')->to('foo#baz');
$foo->get('/bar')->to('#bar');
The actual action code for this destination needs to return a true value or the dispatch chain will be broken. That is a very powerful tool for authentication, no cap ๐ซถ
# /blackjack -> {cb => sub {...}}
# {controller => 'hideout', action => 'blackjack'}
my $auth = $r->under('/' => sub ($c) {
# Authenticated
return 1 if $c->req->headers->header('X-Bender');
# Not authenticated
$c->render(text => "You're not Bender.", status => 401);
return undef;
});
$auth->get('/blackjack')->to('hideout#blackjack');
Broken dispatch chains can be continued by calling continue in Mojolicious::Controller โ which lets you, for example, wait for non-blocking operations to finish before reaching the next dispatch cycle ๐ธ
my $maybe = $r->under('/maybe' => sub ($c) {
# Wait 3 seconds and then give visitors a 50% chance to continue
Mojo::IOLoop->timer(3 => sub {
# Loser
return $c->render(text => 'No luck.') unless int rand 2;
# Winner
$c->continue;
});
return undef;
});
$maybe->get('/')->to('maybe#winner');
Okay so every destination is literally just a snapshot of the stash at the exact moment the route matched, and only the format value is shared by all of them. If you want a little more power, you can introspect the preceding and succeeding destinations with match in Mojolicious::Controller. Obsessed.
# Action of the fourth dispatch cycle
my $action = $c->match->stack->[3]{action};
๐ Formats ๐
File extensions like .html and .txt at the end of a route can be detected and stored in the stash value format. Use a restrictive placeholder to declare the possible values. Itโs giving โI know exactly what formats I accept, bestie.โ
# /foo.txt -> undef
# /foo.rss -> {controller => 'foo', action => 'bar', format => 'rss'}
# /foo.xml -> {controller => 'foo', action => 'bar', format => 'xml'}
$r->get('/foo' => [format => ['rss', 'xml']])->to('foo#bar');
This, for example, allows multiple templates in different formats to share the same action code. And just like with placeholders, you can use a default value to make the format optional. Slay.
# /foo -> {controller => 'foo', action => 'bar'}
# /foo.html -> {controller => 'foo', action => 'bar', format => 'html'}
# /foo.txt -> {controller => 'foo', action => 'bar', format => 'txt'}
$r->get('/foo' => [format => ['html', 'txt']])->to('foo#bar', format => undef);
Formats can also be inherited by nested routes. The way that just works is so iconic.
# /foo -> {controller => 'foo', action => 'one', format => undef}
# /foo.html -> {controller => 'foo', action => 'one', format => 'html'}
# /foo.json -> {controller => 'foo', action => 'one', format => 'json'}
# /bar -> {controller => 'bar', action => 'two', format => undef}
# /bar.html -> {controller => 'bar', action => 'two', format => 'html'}
# /bar.json -> {controller => 'bar', action => 'two', format => 'json'}
my $with_format = $r->any('/' => [format => ['html', 'json']])->to(format => undef);
$with_format->get('/foo')->to('foo#one');
$with_format->get('/bar')->to('bar#two');
A format value can also be passed to url_for in Mojolicious::Controller. Periodt.
# /foo/23.txt -> {controller => 'foo', action => 'bar', id => 23, format => 'txt'}
$r->get('/foo/:id')->to('foo#bar')->name('baz');
# Generate URL "/foo/24.txt" for route "baz"
my $url = $c->url_for('baz', id => 24, format => 'txt');
๐ WebSockets ๐
With the method websocket in Mojolicious::Routes::Route you can restrict access to WebSocket handshakes, which are normal GET requests with some additional information. Yep, theyโre still GET requests โ plot twist.
# /echo (WebSocket handshake)
$r->websocket('/echo')->to('foo#echo');
# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller', -signatures;
# Action
sub echo ($self) {
$self->on(message => sub ($self, $msg) {
$self->send("echo: $msg");
});
}
1;
The connection gets established when you respond to the WebSocket handshake request with a 101 response status. That happens automatically if you subscribe to an event with on in Mojolicious::Controller or send a message with send in Mojolicious::Controller right away. No cap.
GET /echo HTTP/1.1
Host: mojolicious.org
User-Agent: Mojolicious (Perl)
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: IDM3ODE4NDk2MjA1OTcxOQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Server: Mojolicious (Perl)
Date: Tue, 03 Feb 2015 17:08:24 GMT
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: SWsp5N2iNxPbHlcOTIw8ERvyVPY=
๐ธ๏ธ Catch-all route ๐ธ๏ธ
Since routes match in the order in which they were defined, you can catch all requests that did not match in your last route with an optional wildcard placeholder. Itโs the ultimate โif all else fails, bestieโ route.
# * /*
$r->any('/*whatever' => {whatever => ''} => sub ($c) {
my $whatever = $c->param('whatever');
$c->render(text => "/$whatever did not match.", status => 404);
});
๐ง Conditions ๐ง
Conditions such as headers, agent and host from Mojolicious::Plugin::HeaderCondition can be applied to any route with the method requires in Mojolicious::Routes::Route, and allow even more powerful route constructs. Highkey useful.
# / (Origin: http://perl.org)
$r->get('/')->requires(headers => {Origin => qr/perl\.org/})->to('foo#bar');
# / (Firefox)
$r->get('/')->requires(agent => qr/Firefox/)->to('browser-test#firefox');
# / (Internet Explorer)
$r->get('/')->requires(agent => qr/Internet Explorer/)->to('browser-test#ie');
# http://docs.mojolicious.org/Mojolicious
$r->get('/')->requires(host => 'docs.mojolicious.org')->to('perldoc#index');
โ ๏ธ Just be aware that conditions are too complex for the routing cache, which normally speeds up recurring requests, and can therefore reduce performance. So use them with intention, okay? ๐
๐ช Hooks ๐ช
Hooks operate outside the routing system and allow you to extend the framework itself by sharing code with all requests indiscriminately through hook in Mojolicious, which makes them a very powerful tool โ especially for plugins. Main character energy fr fr.
# Application
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
sub startup ($self) {
# Check all requests for a "/test" prefix
$self->hook(before_dispatch => sub ($c) {
$c->render(text => 'This request did not reach the router.') if $c->req->url->path->contains('/test');
});
# These will not be reached if the hook above renders a response
my $r = $self->routes;
$r->get('/welcome')->to('foo#welcome');
$r->post('/bye')->to('foo#bye');
}
1;
Post-processing the response to add or remove headers is a very common use. Literally everyone does it.
# Make sure static files are cached
$app->hook(after_static => sub ($c) {
$c->res->headers->cache_control('max-age=3600, must-revalidate');
});
# Remove a default header
$app->hook(after_dispatch => sub ($c) {
$c->res->headers->remove('Server');
});
Same for pre-processing the request. Vibe check the headers before anything else happens.
# Choose template variant based on request headers
$app->hook(before_dispatch => sub ($c) {
return unless my $agent = $c->req->headers->user_agent;
$c->stash(variant => 'ie') if $agent =~ /Internet Explorer/;
});
Or more advanced extensions to add monitoring to your application. Very โI added observability and now I feel powerful.โ
# Forward exceptions to a web service
$app->hook(after_dispatch => sub ($c) {
return unless my $e = $c->stash('exception');
$c->ua->post('https://example.com/bugs' => form => {exception => $e});
});
You can even extend much of the core functionality. Not me being obsessed with around_action โ okay maybe a little.
# Make controller object available to actions as $_
$app->hook(around_action => sub ($next, $c, $action, $last) {
local $_ = $c;
return $next->();
});
# Pass route name as argument to actions
$app->hook(around_action => sub ($next, $c, $action, $last) {
return $c->$action($c->current_route);
});
For the full list of available hooks, see "HOOKS" in Mojolicious. ๐ซถ
โจ ADVANCED โจ
Okay bestie, this is the good stuff โ the less commonly used but way more powerful features. Buckle up ๐
๐ Shortcuts
So route generation can be way more expressive if you just add your own shortcuts with add_shortcut in Mojolicious::Routes. It's giving "build your own route factory" and honestly? Iconic.
# Simple "resource" shortcut
$r->add_shortcut(resource => sub ($r, $name) {
# Prefix for resource
my $resource = $r->any("/$name")->to("$name#");
# Render a list of resources
$resource->get('/')->to('#index')->name($name);
# Render a form to create a new resource (submitted to "store")
$resource->get('/create')->to('#create')->name("create_$name");
# Store newly created resource (submitted by "create")
$resource->post->to('#store')->name("store_$name");
# Render a specific resource
$resource->get('/:id')->to('#show')->name("show_$name");
# Render a form to edit a resource (submitted by "update")
$resource->get('/:id/edit')->to('#edit')->name("edit_$name");
# Store updated resource (submitted by "edit")
$resource->put('/:id')->to('#update')->name("update_$name");
# Remove a resource
$resource->delete('/:id')->to('#remove')->name("remove_$name");
return $resource;
});
# GET /users -> {controller => 'users', action => 'index'}
# GET /users/create -> {controller => 'users', action => 'create'}
# POST /users -> {controller => 'users', action => 'store'}
# GET /users/23 -> {controller => 'users', action => 'show', id => 23}
# GET /users/23/edit -> {controller => 'users', action => 'edit', id => 23}
# PUT /users/23 -> {controller => 'users', action => 'update', id => 23}
# DELETE /users/23 -> {controller => 'users', action => 'remove', id => 23}
$r->resource('users');
Look at that. One little shortcut and you've got a whole CRUD resource. Slay. ๐
๐ Rearranging routes
From application startup all the way until that very first request arrives, every route can still be moved around โ or even straight-up removed โ with methods like add_child in Mojolicious::Routes::Route and remove in Mojolicious::Routes::Route.
# GET /example/show -> {controller => 'example', action => 'show'}
my $show = $r->get('/show')->to('example#show');
$r->any('/example')->add_child($show);
# Nothing
$r->get('/secrets/show')->to('secrets#show')->name('show_secrets');
$r->find('show_secrets')->remove;
Not me deleting a route before it ever gets to do anything ๐ญ That last one is literally nothing. Gone. Never existed.
This is especially useful for rearranging routes created by plugins, and if you wanna find routes by their name, use find in Mojolicious::Routes::Route.
# GET /example/test -> {controller => 'example', action => 'test'}
$r->get('/something/else')->to('something#else')->name('test');
my $test = $r->find('test');
$test->pattern->parse('/example/test');
$test->pattern->defaults({controller => 'example', action => 'test'});
And yes, even the route pattern and the destination can still be changed โ that's parse in Mojolicious::Routes::Pattern and defaults in Mojolicious::Routes::Pattern doing the lord's work. ๐ซถ
๐ง Adding conditions
You can also add your own conditions with the method add_condition in Mojolicious::Routes. Lowkey these are just router plugins that run every single time a new request arrives, and they need to return a true value for the route to match. No true value, no match, no entry. Periodt.
# A condition that randomly allows a route to match
$r->add_condition(random => sub ($route, $c, $captures, $num) {
# Loser
return undef if int rand $num;
# Winner
return 1;
});
# /maybe (25% chance)
$r->get('/maybe')->requires(random => 4)->to('foo#bar');
A 25% chance the route even exists for you? The audacity. The drama. Obsessed. ๐ฒ
Use whatever request information you need:
# A condition to check query parameters (useful for mock web services)
$r->add_condition(query => sub ($route, $c, $captures, $hash) {
for my $key (keys %$hash) {
my $param = $c->req->url->query->param($key);
return undef unless defined $param && $param eq $hash->{$key};
}
return 1;
});
# /hello?to=world&test=1
$r->get('/hello')->requires(query => {test => 1, to => 'world'})->to('foo#bar');
Super handy for mocking web services, iykyk. โจ
๐บ Condition plugins
You can also package your conditions as reusable plugins, because sharing is caring, bestie.
# Plugin
package Mojolicious::Plugin::WerewolfCondition;
use Mojo::Base 'Mojolicious::Plugin', -signatures;
use Astro::MoonPhase;
sub register ($self, $app, $conf) {
# Add "werewolf" condition
$app->routes->add_condition(werewolf => sub ($route, $c, $captures, $days) {
# Keep the werewolves out!
return undef if abs(14 - (phase(time))[2]) > ($days / 2);
# It's ok, no werewolf
return 1;
});
}
1;
A werewolf condition. Based on the actual moon phase. This is main character energy and I will not be taking questions. ๐
Now you just load the plugin and you're ready to use the condition in all your applications:
# Application
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
sub startup ($self) {
# Plugin
$self->plugin('WerewolfCondition');
# /hideout (keep them out for 4 days after full moon)
$self->routes->get('/hideout')->requires(werewolf => 4)->to(controller => 'foo', action => 'bar');
}
1;
๐ฐ Mount applications
The easiest way to embed one application into another is Mojolicious::Plugin::Mount, which lets you mount whole self-contained applications under a domain and/or prefix. Whole apps. Nested. Like a little app matryoshka doll. ๐
use Mojolicious::Lite -signatures;
# Whole application mounted under "/prefix"
plugin Mount => {'/prefix' => '/home/sri/myapp/script/myapp'};
# Mount application with subdomain
plugin Mount => {'test.example.com' => '/home/sri/myapp2.pl'};
# Normal route
get '/' => sub ($c) {
$c->render(text => 'Hello World!');
};
app->start;
๐ช Embed applications
For a little more power, you can also embed applications by using them instead of a controller. This means, for example, you can use the Mojolicious::Lite domain specific language in normal Mojolicious controllers. Wild, right? ๐ญ
# Controller
package MyApp::Controller::Bar;
use Mojolicious::Lite -signatures;
# /hello
get '/hello' => sub ($c) {
my $name = $c->param('name');
$c->render(text => "Hello $name.");
};
1;
With the attribute partial in Mojolicious::Routes::Route, you can let the route partially match and use only the remaining path in the embedded application โ and the base path gets passed along in the path stash value. ๐ซ
# /foo/*
$r->any('/foo')->partial(1)->to('bar#', name => 'Mojo');
A minimal embeddable application is nothing more than a subclass of Mojolicious containing a handler method that accepts Mojolicious::Controller objects. That's it. That's the whole requirement.
package MyApp::Controller::Bar;
use Mojo::Base 'Mojolicious', -signatures;
sub handler ($self, $c) {
$c->res->code(200);
my $name = $c->param('name');
$c->res->body("Hello $name.");
}
1;
Heads up bestie: the host application only shares very little information with the embedded application through the stash. So you cannot currently use route placeholders in routes leading to embedded applications, because that would cause problems with url_for in Mojolicious::Controller. Not a vibe, but it's the rules. ๐ซ
๐ฆ Application plugins
You can even package applications as self-contained reusable plugins. Little app, big business. ๐
# Plugin
package Mojolicious::Plugin::MyEmbeddedApp;
use Mojo::Base 'Mojolicious::Plugin', -signatures;
sub register ($self, $app, $conf) {
# Automatically add route
$app->routes->any('/foo')->partial(1)->to(app => EmbeddedApp::app());
}
package EmbeddedApp;
use Mojolicious::Lite;
get '/bar' => 'bar';
1;
__DATA__
@@ bar.html.ep
Hello World!
The app stash value โ which won't be inherited by nested routes โ can be used for already instantiated applications. Now just load the plugin and you're done. ๐
# Application
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
sub startup ($self) {
# Plugin
$self->plugin('MyEmbeddedApp');
}
1;
๐ MORE
You can continue with Mojolicious::Guides now, or take a look at the Mojolicious wiki at https://github.com/mojolicious/mojo/wiki โ it's got a lot more documentation and examples by many different authors. Go get lost in there, it's a whole vibe. ๐ฆ
๐ SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the Forum at https://forum.mojolicious.org, or on IRC at https://web.libera.chat/#mojo. The community is sweet, I promise. ๐ซถ
And that's a wrap on the whole page, bestie! ๐ This one shipped as perl v5.42.0, dated 2026-07-15 โ go forth and route responsibly. Mwah! ๐โจ
๐ฆ where this page comes from
- opensuse-leap 16.0: perl-Mojolicious 9.480.0-bp160.2.1 ยท Artistic-2.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms