The foundation of every web framework is a tiny black box connecting incoming requests with code generating the appropriate response.

GET /user/show/1 -> $c->render(text => 'Sebastian');

This black box is usually called a dispatcher. There are many implementations using different strategies to establish these connections, but pretty much all are based around mapping the path part of the request URL to some kind of response generator.

While it is very well possible to make all these connections static, it is also rather inefficient. That's why regular expressions are commonly used to make the dispatch process more dynamic.

qr!/user/show/(\d+)! -> $c->render(text => $users{$1});

Modern dispatchers have pretty much everything HTTP has to offer at their disposal and can use many more variables than just the request path, such as request method and headers like Host, User-Agent and Accept.

While regular expressions are quite powerful they also tend to be unpleasant to look at and are generally overkill for ordinary path matching.

qr!/user/show/(\d+)! -> $c->render(text => $users{$1});

This is where routes come into play, they have been designed from the ground up to represent paths with placeholders.

/user/show/:id -> $c->render(text => $users{$id});

The only difference between a static path and the route above is the :id placeholder. One or more placeholders can be anywhere in the route.

/user/:action/:id

A fundamental concept of the Mojolicious router is that extracted placeholder values are turned into a hash.

/user/show/23 -> /user/:action/:id -> {action => 'show', id => 23}

This hash is basically the center of every Mojolicious application, you will learn more about this later on. Internally routes get compiled to regular expressions, so you can get the best of both worlds with a little bit of experience.

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.

When the dispatcher sees controller and action values in the stash it will always try to turn them into a class and method to dispatch to. The controller value gets converted from snake_case to CamelCase using "camelize" in Mojo::Util and appended to one or more namespaces, defaulting to a controller namespace based on the application class (MyApp::Controller), as well as the bare application class (MyApp), and these namespaces are searched in that order. The action value is not changed at all, so both values are case-sensitive.

Controller classes are perfect for organizing code in larger projects. There are more dispatch strategies, but because controllers are the most commonly used ones they also got a special shortcut in the form of controller#action.

You can also change the default namespaces for all routes in the application 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).

You can assign a name with "name" in Mojolicious::Routes::Route, or let the router generate one automatically, which would be equal to the route itself without non-word characters, custom names have a higher precedence though.

Special stash values like controller and action can also be placeholders, which is very convenient especially during development, but should only be used very carefully, because every controller method becomes a potential route. All uppercase methods as well as those starting with an underscore are automatically hidden from the router and you can use "hide" in Mojolicious::Routes to add additional ones.

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, (?:...) is fine though.

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.

Every destination is just a snapshot of the stash at the time the route matched, and only the format value is shared by all of them. For a little more power you can introspect the preceding and succeeding destinations with "match" in Mojolicious::Controller.

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.

You can also add your own conditions with the method "add_condition" in Mojolicious::Routes. All conditions are basically router plugins that run every time a new request arrives, and which need to return a true value for the route to match.

For a litte more power you can also embed applications by using them instead of a controller. This allows for example the use of the Mojolicious::Lite domain specific language in normal Mojolicious controllers.