Dancer::Cookbook(3pm)

๐Ÿ’– NAME ๐Ÿ’–

Dancer::Cookbook โ€” a quick-start guide to the Dancer web framework. (It's the Dancer::Cookbook(3pm) page from the User Contributed Perl Documentation, we're just doing it with โœจmain character energyโœจ now.)


๐Ÿ”ข VERSION ๐Ÿ”ข

version 1.3522


๐ŸŒˆ DESCRIPTION ๐ŸŒˆ

A quick-start guide with examples to get you up and running with the Dancer web framework. That's it, that's the vibe โ€” examples, and you, dancing. ๐Ÿ•บ


๐Ÿ’ƒ BEGINNER'S DANCE ๐Ÿ’ƒ

๐Ÿฃ Your first Dancer web app

okay so. Dancer was literally designed to be easy to work with โ€” it's trivial to write a simple web app, but it still has the power to carry bigger projects, no cap. To start with, let's make an incredibly simple "Hello World" example:

#!/usr/bin/perl

use Dancer;

get '/hello/:name' => sub {
    return "Why, hello there " . params->{name};
};

dance;

Yes bestie, the above is a fully-functioning web app. Running that script launches a webserver listening on the default port, which is 3000. Now you can make a request:

$ curl http://localhost:3000/hello/Bob
Why, hello there Bob

(or the name of the machine you ran it on, if it's not your local system), and it will say hello. The ":name" part is a named parameter inside the route specification, whose value gets made available through "params" โ€” more on that later, iykyk.

โœจ Note that you don't need to use the "strict" and "warnings" pragma โ€” they're already loaded by Dancer. If you don't want the "warnings" pragma (which can lead to undesired warnings about use of undef values, for example), then set the import_warnings setting to a false value. Easy.

๐Ÿš€ Starting a Dancer project

The first simple example is fine for trivial projects, but for anything more complex you'll want a more maintainable solution โ€” enter the "dancer" helper script, which builds the whole framework of your application with a single command:

$ dancer -a mywebapp
+ mywebapp
+ mywebapp/bin
+ mywebapp/bin/app.pl
+ mywebapp/config.yml
+ mywebapp/environments
+ mywebapp/environments/development.yml
+ mywebapp/environments/production.yml
+ mywebapp/views
+ mywebapp/views/index.tt
+ mywebapp/views/layouts
+ mywebapp/views/layouts/main.tt
+ mywebapp/MANIFEST.SKIP
+ mywebapp/lib
+ mywebapp/lib/mywebapp.pm
+ mywebapp/public
+ mywebapp/public/css
+ mywebapp/public/css/style.css
+ mywebapp/public/css/error.css
+ mywebapp/public/images
+ mywebapp/public/500.html
+ mywebapp/public/404.html
+ mywebapp/public/dispatch.fcgi
+ mywebapp/public/dispatch.cgi
+ mywebapp/public/javascripts
+ mywebapp/public/javascripts/jquery.min.js
+ mywebapp/t
+ mywebapp/t/002_index_route.t
+ mywebapp/t/001_base.t
+ mywebapp/Makefile.PL

As you can see, it creates a directory named after the name of the app, along with:

  • a configuration file
  • a views directory (where your templates and layouts will live)
  • an environments directory (where environment-specific settings live)
  • a module containing the actual guts of your application
  • a script to start it โ€” or to run your web app via Plack/PSGI โ€” more on that later

๐Ÿ›ฃ๏ธ DANCE ROUTINES: ROUTES ๐Ÿ›ฃ๏ธ

๐Ÿ“ Declaring routes

To control what happens when a web request is received by your webapp, you'll need to declare "routes". A route declaration says three things: which HTTP method(s) it is valid for, the path it matches (e.g. /foo/bar), and a coderef to execute, which returns the response. Like this:

get '/hello/:name' => sub {
    return "Hi there " . params->{name};
};

The above route specifies that, for GET requests to '/hello/...', the code block provided should be executed. ๐Ÿ’…

๐Ÿ”€ Handling multiple HTTP request methods

Routes can use "any" to match all HTTP methods, or a specified list of them.

The following will match any HTTP request to the path /myaction:

any '/myaction' => sub {
    # code
}

The following will match GET or POST requests to /myaction:

any ['get', 'post'] => '/myaction' => sub {
    # code
};

For convenience, any route which matches GET requests will also match HEAD requests. Slay. โœจ

๐ŸŽ Retrieving request parameters

The params keyword returns a hashref of request parameters. These will be parameters supplied on the query string, within the path itself (with named placeholders), and, for HTTP POST requests, the content of the POST body. All of it, right there in params. ๐Ÿซถ

๐Ÿท๏ธ Named parameters in route path declarations

As seen above, you can use ":somename" in a route's path to capture part of the path; this will become available by calling params.

So, for a web app where you want to display information on a company, you might use something like:

get '/company/view/:companyid' => sub {
    my $company_id = params->{companyid};
    # Look up the company and return appropriate page
};

๐Ÿƒ Wildcard path matching and splat

You can also declare wildcards in a path, and retrieve the values they matched with the splat keyword:

get '/*/*' => sub {
    my ($action, $id) = splat;
    if (my $action eq 'view') {
        return display_item($id);
    } elsif ($action eq 'delete') {
        return delete_item($id);
    } else {
        status 'not_found';
        return "What?";
    }
};

๐Ÿช Before hooks - processed before a request

A before hook declares code which should be handled before a request is passed to the appropriate route.

hook 'before' => sub {
    var note => 'Hi there';
    request->path_info('/foo/oversee')
};

get '/foo/*' => sub {
    my ($match) = splat; # 'oversee';
    vars->{note}; # 'Hi there'
};

The above declares a before hook which uses "var" to set a variable that will later be available within the route handler, then amends the path of the request to "/foo/oversee". Which means: whatever path was requested, it will be treated as though the path requested was "/foo/oversee". Sneaky. Iconic. ๐Ÿงš

๐Ÿšจ Default route

In case you want to avoid a 404 error, or handle multiple routes in the same way and you don't feel like configuring all of them, you can set up a default route handler.

The default route handler will handle any request that doesn't get served by any other route.

All you need to do is set up the following route as the last route:

any qr{.*} => sub {
    status 'not_found';
    template 'special_404', { path => request->path };
};

Then you can set up the template as such:

You tried to reach <% path %>, but it is unavailable at the moment.

Please try again or contact us at our email at <...>.

๐Ÿช„ Using the auto_page feature for automatic route creation

For simple "static" pages, you can simply enable the "auto_page" config setting. This means you need not declare a route handler for those pages: if a request is for "/foo/bar", Dancer will check for a matching view (e.g. "/foo/bar.tt") and render it with the default layout etc if found. For full details, see the documentation for the auto_page setting.

๐ŸŒ Why should I use the Ajax plugin

As an Ajax query is just an HTTP query, it's similar to a GET or POST route. You may ask yourself why you'd want to use the "ajax" keyword (from the Dancer::Plugin::Ajax plugin) instead of a simple "get".

Let's say you have a path like '/user/:user' in your application. You may want to be able to serve this page, with a layout and HTML content. But you may also want to be able to call this same url from a javascript query using Ajax.

So, instead of having the following code:

get '/user/:user' => sub {
     if (request->is_ajax) {
         # create xml, set headers to text/xml, blablabla
          header('Content-Type' => 'text/xml');
          header('Cache-Control' =>  'no-store, no-cache, must-revalidate');
          to_xml({...})
     }else{
         template users, {....}
     }
};

you can have

get '/user/:user' => sub {
    template users, {...}
}

and

ajax '/user/:user' => sub {
     to_xml({...}, RootName => undef);
}

Because it's an Ajax query you know you need to return XML content, so the content type of the response is set for you. Obsessed. ๐Ÿ˜Œ

โœ‚๏ธ Using the prefix feature to split your application

For better maintainability you may want to separate some of your application components into different packages. Let's say we have a simple web app with an admin section, and want to maintain this in a different package:

package myapp;
use Dancer ':syntax';
use myapp::admin;

prefix undef;

get '/' => sub {...};

1;

package myapp::admin;
use Dancer ':syntax';

prefix '/admin';

get '/' => sub {...};

1;

The following routes will be generated for us:

- get /
- get /admin/
- head /
- head /admin/

๐Ÿ’พ MUSCLE MEMORY: STORING DATA ๐Ÿ’พ

๐Ÿช Handling sessions

okay bestie so, sessions? absolute staple. you want your web app to remember stuff โ€” like, a user logs in, gets a session, and then every request after that you check the session and go "oh hey, it's you, you're still that person." that whole vibe. ๐Ÿ’…

to use sessions you gotta enable the session engine first. pick your engine, then declare it in your config file:

session: Simple

the Dancer::Session::Simple backend is in-memory session storage โ€” super duper simple. it's fast, it's great for testing, but heads up: sessions do not persist between restarts of your app. nope. gone. ๐Ÿ’€

you can also use the Dancer::Session::YAML backend that ships with Dancer, which writes session data to disc in YAML files. and since YAML is a lovely human-readable format, peeking at what's inside your sessions is an absolute breeze:

session: YAML

or, if you wanna turn on session support from inside your actual code:

set session => 'YAML';
(controlling settings is best done from your config file though, fr fr โ€” code is not the main character here.)

in that example, 'YAML' is the session backend you're picking; it's shorthand for Dancer::Session::YAML. there are other session backends out there you might wanna use, like Dancer::Session::Memcache, but the YAML backend is a simple, easy example that stores session data in a YAML file in sessions. ๐ŸŽ€

once that's sorted, you can use the session keyword to mess with the session, like so:

๐Ÿ“ฅ Storing data in the session

storing data in the session? literally as easy as:

session varname => 'value';

๐Ÿ“ค Retrieving data from the session

getting data back out of the session? also easy, bestie:

session('varname')

or, alternatively:

session->{varname}

๐Ÿ“ Controlling where sessions are stored

for disc-based session back ends like Dancer::Session::YAML, Dancer::Session::Storable etc, session files get written to the session dir specified by the session_dir setting โ€” and that defaults to appdir/sessions.

need to control where those session files land? quick and easy, right in your config file. for example:

session_dir: /tmp/dancer-sessions

and if the directory you name doesn't exist yet, Dancer will attempt to create it for you. helpful queen. โœจ

๐Ÿ’ฅ Destroying a session

when you're done with your session, you can destroy it:

session->destroy

๐Ÿ”‘ Sessions and logging in

a super common requirement: check if the user is logged in, and if they're not, make them log in before they can keep going.

this is easily handled with a before hook that checks their session:

hook 'before' => sub {
    if (! session('user') && request->path_info !~ m{^/login}) {
        var requested_path => request->path_info;
        request->path_info('/login');
    }
};

get '/login' => sub {
    # Display a login page; the original URL they requested is available as
    # vars->{requested_path}, so could be put in a hidden field in the form
    template 'login', { path => vars->{requested_path} };
};

post '/login' => sub {
    # Validate the username and password they supplied
    if (params->{user} eq 'bob' && params->{pass} eq 'letmein') {
        session user => params->{user};
        redirect params->{path} || '/';
    } else {
        redirect '/login?failed=1';
    }
};

in your login page template you'll want a text field named user, a password field named pass, and a hidden field named path. that hidden field gets populated with the path they originally asked for, so it gets sent back along with the POST submission, and then your post route can use it to redirect onwards to the page they originally wanted once they're logged in. chef's kiss. ๐Ÿ‘‘

obviously you'll probably wanna validate your users against a database table, or maybe via IMAP/LDAP/SSH/POP3/local system accounts via PAM, etc etc. Authen::Simple is probably a good starting point here!

and here's a simple working example of handling authentication against a database table yourself โ€” it uses Dancer::Plugin::Database (which gives you the database keyword) and Crypt::SaltedHash to handle salted hashed passwords (because bestie, you wouldn't store your users' passwords in the clear, would you?? ๐Ÿ™ˆ):

post '/login' => sub {
    my $user = database->quick_select('users',
        { username => params->{user} }
    );
    if (!$user) {
        warning "Failed login for unrecognised user " . params->{user};
        redirect '/login?failed=1';
    } else {
        if (Crypt::SaltedHash->validate($user->{password}, params->{pass}))
        {
            debug "Password correct";
            # Logged in successfully
            session user => $user;
            redirect params->{path} || '/';
        } else {
            debug("Login failed - password incorrect for " . params->{user});
            redirect '/login?failed=1';
        }
    }
};

๐Ÿ—‚ Retrieve complete hash stored in session

get the complete hash stored in the session:

my $hash = session;

โœจ APPEARANCE โœจ

๐ŸŽจ Using templates - views and layouts

returning plain content is all well and good for examples or tiny trivial apps, but pretty soon you're gonna want templates, so your code and your content can live separate lives. Dancer makes this easy, no cap.

your route handlers can use the template keyword to render templates. ๐Ÿ’–

๐Ÿ–ผ Views

you can render an action's content with a template โ€” that's called a view. the appdir/views directory is where views hang out.

you can change that location by changing the views setting.

by default, the internal template engine Dancer::Template::Simple is used, but you might wanna upgrade to Template::Toolkit. if you do, you have to enable that engine in your settings, as explained in Dancer::Template::TemplateToolkit. and if you do that, you'll also have to import the Template module in your application code. just so you know. ๐ŸŒธ

note that by default, Dancer configures the Template::Toolkit engine to use <% %> brackets instead of its default [% %] brackets. you can change this with the following in your config file:

template: template_toolkit

engines:
    template_toolkit:
        start_tag: '[%'
        stop_tag: '%]'

all views must have a .tt extension. this may change in the future. (iykyk.)

to render a view, just call the template keyword at the end of the action, handing it the view name and the HASHREF of tokens to interpolate in the view. and fun fact for convenience: the request, session, params and vars are automatically accessible in the view, named request, session, params and vars. for example:

hook 'before' => sub { var time => scalar(localtime) };

get '/hello/:name' => sub {
    my $name = params->{name};
    template 'hello.tt', { name => $name };
};

and the template 'hello.tt' could contain, for example:

<p>Hi there, <% name %>!</p>
<p>You're using <% request.user_agent %></p>
<% IF session.username %>
    <p>You're logged in as <% session.username %>
<% END %>
It's currently <% vars.time %>

for a full list of the tokens that get automatically added to your template (like session, request and vars), go check out Dancer::Template::Abstract.

๐Ÿงฉ Layouts

a layout is a special view, living in the layouts directory (inside the views directory), and it must have a token named content. that token marks the spot where the action view gets rendered. this lets you define one global layout for your actions, and have each individual view contain only its specific content. it's a great way to dodge a ton of needless HTML duplication :) ๐Ÿ’œ

here's an example of a layout, views/layouts/main.tt:

<html>
    <head>...</head>
    <body>
    <div id="header">
    ...
    </div>

    <div id="content">
    <% content %>
    </div>

    </body>
</html>

you can tell your app which layout to use with layout: name in the config file, or right within your code:

set layout => 'main';

and you can control which layout gets used (or whether a layout gets used at all) for one specific request without touching the layout setting, by passing an options hashref as the third param to the template keyword:

template 'index.tt', {}, { layout => undef };

if your application isn't mounted under root (/), you can use a before_template_render hook instead of hardcoding the path to your app for your css, images and javascript:

hook 'before_template_render' => sub {
    my $tokens = shift;
    $tokens->{uri_base} = request->base->path;
};

then in your layout, tweak your css inclusion like this:

<link rel="stylesheet" href="<% uri_base %>/css/style.css" />

and from now on you can mount your application wherever you want without any further modification of the css inclusion. iconic. ๐ŸŒˆ

๐ŸŒ template and unicode

if you use Plack and you're hitting some unicode problem with your Dancer application, don't forget to check whether you've set your template engine to use unicode, and set the default charset to UTF-8. so if you're using template toolkit, your config.yml will look like this:

charset: UTF-8
engines:
  template_toolkit:
    ENCODING: utf8

๐Ÿ”ฎ TT's WRAPPER directive in Dancer (META variables, SETs)

Dancer already gives you a WRAPPER-like ability, and we call it a "layout". the reason we don't use TT's WRAPPER (which also makes it incompatible with Dancer) is because not all template systems support it. actually, most don't.

but hey, you might wanna use it, and be able to define META variables and regular Template::Toolkit variables.

these few steps will get you there:

  1. Disable the layout in Dancer

you can do this by simply commenting out (or removing) the layout configuration in the config.yml file.

  1. Use Template Toolkit template engine

change the configuration of the template to Template Toolkit:

# in config.yml
template: "template_toolkit"
  1. Tell the Template Toolkit engine who's your wrapper
# in config.yml
# ...
engines:
    template_toolkit:
        WRAPPER: layouts/main.tt

done! everything will work fine out of the box, including variables and META variables. slay. ๐Ÿฆ‹

โœจ SETTING THE STAGE: CONFIGURATION AND LOGGING โœจ

๐ŸŽ€ Configuration and environments ๐ŸŽ€

okay bestie so configuring your Dancer app? there are so many ways and honestly they range from "fine" to "elite". โœจ

The easiest one (and maybe the dirtiest, no shade) is to just dump all your settings statements at the top of your script, before you call the dance() method. boom. done. chaotic but done. ๐Ÿ’…

Other ways are possible though. You can define all your settings in the file appdir/config.yml. For this, you must have installed the YAML module, and of course, write the config file in YAML. ๐Ÿ“

That's better than the first option, but it's still not perfect as you can't switch easily from one environment to another without rewriting the config.yml file. ๐Ÿ˜ญ lowkey tragic.

The better way is to have one config.yml file with default global settings, like the following:

# appdir/config.yml
logger: 'file'
layout: 'main'

And then write as many environment files as you like in appdir/environments. That way the appropriate environment config file will be loaded according to the running environment (if none is specified, it will be 'development'). ๐Ÿฆ‹

Note that you can change the running environment using the --environment command line switch. ๐Ÿ‘‘

Typically, you'll want to set the following values in a development config file:

# appdir/environments/development.yml
log: 'debug'
startup_info: 1
show_errors:  1

And in a production one:

# appdir/environments/production.yml
log: 'warning'
startup_info: 0
show_errors:  0

periodt. two vibes, two files, zero rewriting. ๐ŸŒˆ


๐Ÿงš Accessing configuration information from your app ๐Ÿงš

A Dancer application can use the 'config' keyword to easily access the settings within its config file, for instance:

get '/appname' => sub {
    return "This is " . config->{appname};
};

This makes keeping your application's settings all in one place simple and easy. You shouldn't need to worry about implementing all that yourself :) ๐Ÿซถ like, it's giving "handled".


๐Ÿ’œ Accessing configuration information from a separate script ๐Ÿ’œ

You may well want to access your webapp's configuration from outside your webapp. You could, of course, use the YAML module of your choice and load your webapps's config.yml, but chances are that this is not convenient. ๐Ÿ™ˆ

Use Dancer instead. Without any ado, magic or too big jumps, you can use the values from config.yml and some additional default values:

# bin/script1.pl
use Dancer ':script';
print "template:".config->{template}."\n"; #simple
print "log:".config->{log}."\n"; #undef

Note that config->{log} should result in an undef error on a default scaffold since you did not load the environment and in the default scaffold log is defined in the environment and not in config.yml. Hence undef. ๐Ÿ’€ that's not a bug bestie that's just the scaffolding being like that.

If you want to load an environment you need to tell Dancer where to look for it. One way to do so, is to tell Dancer where the webapp lives. From there Dancer deduces where the config.yml file is (typically $webapp/config.yml). ๐Ÿ”

# bin/script2.pl
use FindBin;
use Cwd qw/realpath/;
use Dancer ':script';

#tell the Dancer where the app lives
my $appdir=realpath( "$FindBin::Bin/..");

Dancer::Config::setting('appdir',$appdir);
Dancer::Config::load();

#getter
print "environment:".config->{environment}."\n"; #development
print "log:".config->{log}."\n"; #value from development environment

By default Dancer loads development environment (typically $webapp/environment/development.yml). In contrast to the example before, you do have a value from the development environment (environment/development.yml) now. Also note that in the above example Cwd and FindBin are used. They are likely to be already loaded by Dancer anyways, so it's not a big overhead. You could just as well hand over a simple path for the app if you like that better, e.g.:

Dancer::Config::setting('appdir','/path/to/app/dir');

If you want to load an environment other than the default, try this:

# bin/script2.pl
use Dancer ':script';

#tell the Dancer where the app lives
Dancer::Config::setting('appdir','/path/to/app/dir');

#which environment to load
config->{environment}='production';

Dancer::Config::load();

#getter
print "log:".config->{log}."\n"; #has value from production environment

By the way, you not only get values, you can also set values straightforward like we do above with config->{environment}='production'. Of course, this value does not get written in any file; it only lives in memory and your webapp doesn't have access to it, but you can use it inside your script. ๐Ÿง  in-memory only, bestie, it's a vibe not a document.

If you don't want to make your script environment-specific, or add extra arguments to it, you can also set the environment using a shell variable, DANCER_ENVIRONMENT. See also ["DANCER_CONFDIR-and-DANCER_ENVDIR" in Dancer::Config](https://metacpan.org/pod/Dancer::Config) โ€” well, no links, but you get it. ๐Ÿ’–


๐Ÿ“ Logging ๐Ÿ“

Configuring logging

It's possible to log messages generated by the application and by Dancer itself. ๐Ÿ‘‘

To start logging, select the logging engine you wish to use with the "logger" setting; Dancer includes built-in log engines named "file" and "console", which log to a logfile and to the console respectively. ๐Ÿ“„๐Ÿ–ฅ๏ธ

To enable logging to a file, add the following to your config.yml:

logger: 'file'

Then you can choose which kind of messages you want to actually log:

log: 'core'      # will log all messages, including messages from
                 # Dancer itself
log: 'debug'     # will log debug, info, warning and error messages
log: 'info'      # will log info, warning and error messages
log: 'warning'   # will log warning and error messages
log: 'error'     # will log error messages

If you're using the "file" logging engine, a directory appdir/logs will be created and will host one logfile per environment. The log message contains the time it was written, the PID of the current process, the message and the caller information (file and line). ๐Ÿ•ฐ๏ธ so much metadata, we love a detailed receipt.

Logging your own messages

Just call debug, warning, error or info with your message:

debug "This is a debug message from my app.";

๐Ÿ˜ด RESTING ๐Ÿ˜ด

๐Ÿ’… Writing a REST application ๐Ÿ’…

With Dancer, it's easy to write REST applications. Dancer provides helpers to serialize and deserialize for the following data formats:

  • JSON
  • YAML
  • XML
  • Data::Dumper

To activate this feature, you only have to set the "serializer" setting to the format you require, for instance in your config.yml:

serializer: JSON

Or right in your code:

set serializer => 'JSON';

From now, all HashRefs or ArrayRefs returned by a route will be serialized to the format you chose, and all data received from POST or PUT requests will be automatically deserialized. โœจ

get '/hello/:name' => sub {
    # this structure will be returned to the client as
    # {"name":"$name"}
    return {name => params->{name}};
};

It's possible to let the client choose which serializer he wants to use. For this, use the mutable serializer, and an appropriate serializer will be chosen from the Content-Type header. ๐Ÿฆ‹

It's also possible to return a custom error, using the send_error keyword.. When you don't use a serializer, the "send_error" function will take a string as the first parameter (the message), and an optional HTTP code. When using a serializer, the message can be a string, an ArrayRef or a HashRef:

get '/hello/:name' => sub {
    if (...) {
       send_error("you can't do that");
       # or
       send_error({reason => 'access denied', message => "no"});
    }
};

The content of the error will be serialized using the appropriate serializer. ๐Ÿ’€ custom errors, custom format, custom everything.


๐Ÿš€ Deploying your Dancer applications ๐Ÿš€

For examples on deploying your Dancer applications including standalone, behind proxy/load-balancing software, and using common web servers including Apache to run via CGI/FastCGI etc, see [Dancer::Deployment](https://metacpan.org/pod/Dancer::Deployment). (ink to the next chapter, iykyk ๐Ÿ’œ)


๐ŸŒŸ DANCER ON THE STAGE: DEPLOYMENT ๐ŸŒŸ

๐Ÿ‘‘ Plack middlewares ๐Ÿ‘‘

If you deploy with Plack and use some Plack middlewares, you can enable them directly from Dancer's configuration files. not me thinking I'd have to do all of that by hand ๐Ÿ˜ญ

Generic middlewares

To enable middlewares in Dancer, you just have to set the plack_middlewares setting like the following:

set plack_middlewares => [
    [ 'SomeMiddleware' => qw(some options for somemiddleware) ],
];

For instance, if you want to enable [Plack::Middleware::Debug](https://metacpan.org/pod/Plack::Middleware::Debug) in your Dancer application, all you have to do is to set "plack_middlewares" like that:

set plack_middlewares => [
    [ 'Debug' => ( 'panels' => [qw(DBITrace Memory Timer)] ) ],
];

Of course, you can also put this configuration into your config.yml file, or even in your environment configuration files:

# environments/development.yml
...
plack_middlewares:
  -
    - Debug          # first element of the array is the name of the middleware
    - panels         # following elements are the configuration of the middleware
    -
        - DBITrace
        - Memory
        - Timer

Path-based middlewares

If you want to set up a middleware for a specific path, you can do that using "plack_middlewares_map". You'll need [Plack::App::URLMap](https://metacpan.org/pod/Plack::App::URLMap) to do that. ๐Ÿ“

plack_middlewares_map:
    '/':      ['Debug']
    '/timer': ['Timer'],

๐Ÿ”ง DEVELOPMENT TOOLS ๐Ÿ”ง

โšก Auto-reloading code โšก

When you are furiously hacking on your Dancer app, it might come in handy to have the application auto-detect changes in the code and reload itself. ๐Ÿ”ฅ

To do that, you can use [Plack::Loader::Shotgun](https://metacpan.org/pod/Plack::Loader::Shotgun), [Plack::Middleware::Refresh](https://metacpan.org/pod/Plack::Middleware::Refresh), or plackup with the -r switch:

plackup -r bin/appl.pl  (will restart the app whenever a file in ./bin or ./lib is modified
heads up bestie, that's the original's own unclosed parenthesis, we keep it exactly as it is. ๐Ÿ’…

ngl, save-on-edit-and-it-just-works is the most iconic dev experience. periodt. ๐ŸŒธ


๐Ÿ’– AUTHOR ๐Ÿ’–

Dancer Core Developers โ€” yes the whole squad, no one gets singled out. ๐Ÿ‘‘


This software is copyright (c) 2010 by Alexis Sukrieh.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


and that's a wrap on the whole cookbook, bestie!! ๐ŸŽ€ this very page you just read is perl v5.40.1, dated 2026-05-05, filed under Dancer::Cookbook(3pm) in the user contributed perl documentation. go build something cute, and remember: the config goes in config.yml, the vibe goes everywhere. ๐Ÿฆ‹โœจ๐Ÿ’–

๐Ÿ“ฆ where this page comes from