Dancer2::Manual(3)

Dancer2::Manual(3) โ€” from the User Contributed Perl Documentation girlies ๐Ÿ’–

๐Ÿ“– NAME ๐Ÿ“–

Dancer2::Manual โ€” a guide to building web applications with Dancer2 โœจ

๐ŸŽ€ VERSION ๐ŸŽ€

version 2.2.1


๐ŸŽข Introduction to Dancer2: Managing Danceyland ๐ŸŽก

okay bestie, welcome to Danceyland!! ๐ŸŒˆ As the brand-new manager of this truly iconic park, your whole job is maintaining and enhancing the experience for every single visitor. Imagine each attraction, each food stall, each ticket booth as a different location in your web application. Let's go explore how to manage all these lil components with Dancer2 ๐Ÿซถ

If you're not sure if you're at the correct spot in the park, the documentation map can help you find your way. ๐Ÿ—บ๏ธ

๐Ÿ’ซ What is Dancer2? ๐Ÿ’ซ

Dancer2 is a free and open-source web application framework written in Perl. It's a complete rewrite of the original Dancer, designed to be powerful and flexible, yet incredibly easy to use.

With Dancer2, getting your web app up and running is a breeze, no cap. It boasts a rich ecosystem of adapters for popular template engines, session storage solutions, logging methods, serializers, and plugins. Which means you can build your app your way, effortlessly ๐Ÿ’… In this guide, we'll leverage those strengths to build and manage Danceyland.

Before we learn the ins and outs of managing your park, make sure you head over to the quick start guide and get your development machine set up with Dancer2. Once it's installed, make sure to build your park:

dancer2 gen -a Danceyland

๐ŸŽ  Routes: Different Attractions and Facilities ๐ŸŽ 

Core to Dancer2 is the concept of routes. Each attraction in your theme park is like a route in your web application. Visitors (users) can navigate to different attractions, just as they would visit different routes in your app โœจ

Let's show some of the rides and facilities in our park:

# Defining routes for our theme park
get '/' => sub {
    return "Welcome to Danceyland!";
};

get '/roller-coaster' => sub {
    return "Enjoy the thrilling roller coaster ride!";
};

post '/buy-ticket' => sub {
    my $ticket = body_parameters->get('ticket');
    # Do something with ticket data
    return "You bought a $ticket ticket!";
};

The / route is like the main entrance to our theme park โ€” visitors get greeted with a welcome message ๐ŸŽ‰ The /roller-coaster route is a thrilling ride. When visitors take this path, they get a special message ๐ŸŽข The /buy-ticket route is the ticket booth. Visitors buy their tickets here ๐ŸŽŸ๏ธ

๐Ÿ”‘ New Keywords ๐Ÿ”‘

  • get โ€” This keyword defines a route that responds to HTTP GET requests. It takes two parameters: the route path and a subroutine that defines the response.
get '/path' => sub {
    return "Response text";
};

Note that a route to match "HEAD" requests is automatically created when you create a "GET" route. (sneaky lil freebie, we love that for us ๐Ÿ’–)

  • post โ€” This keyword defines a route that responds to HTTP POST requests. It works similarly to get, but is used for actions like submitting forms or buying tickets.
post '/path' => sub {
    my $param = body_parameters->get('param');
    return "You submitted: $param";
};

So exactly what are these HTTP requests, and what are they all about? ๐Ÿ‘€

๐Ÿšฆ HTTP Methods: Visitor Actions ๐Ÿšฆ

Think of HTTP methods as the different actions visitors can take in your theme park. Entering the park, buying tickets, updating ticket details, and leaving the park are all actions represented by GET, POST, PUT, DEL, OPTIONS, and PATCH methods respectively.

Handling visitor actions in the park:

# Defining HTTP methods for visitor actions
get '/' => sub {
    return "Welcome to Danceyland!";
};

post '/buy-ticket' => sub {
    my $ticket = body_parameters->get('ticket');
    return "You bought a $ticket ticket!";
};

put '/update-ticket' => sub {
    my $new_ticket = body_parameters->get('new_ticket');
    return "Your ticket has been updated to $new_ticket!";
};

del '/leave-park' => sub {
    return "Thank you for visiting! Please come again!";
};

options '/park/info' => sub {
    return "Allowed methods: GET, POST, PUT";
};

patch '/profile/:id' => sub {
    my $user_id = route_parameters->get('id');
    my $new_email = body_parameters->get('email');
    return "Updated profile for user $user_id with email $new_email";
};
  • GET: Visitors enter the park and see a welcome message ๐ŸŽˆ
  • POST: Visitors buy a ticket ๐ŸŽŸ๏ธ
  • PUT: Visitors update their ticket details โœ๏ธ
  • DELETE: Visitors leave the park ๐Ÿ‘‹
  • PATCH: Update part of a visitor's profile (in this case, email) ๐Ÿ“ง
  • OPTIONS: Describing the available operations on a specific route. Keep in mind, you would rarely implement this in your web application ๐Ÿซฃ

These are good conventions to follow, but you can make your route handlers do whatever makes the most sense for your application. Your park, your rules bestie ๐Ÿ‘‘

๐Ÿงฉ Routes, Route Definitions, and Route Handlers ๐Ÿงฉ

You may hear other Dancer developers talk about "routes", "route definitions", and "route handlers". Route definitions refers to the HTTP method and URL to respond to, while route handler is only the code implementing functionality for that definition. The two of these together make what's known as a route.

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

  • The verb, path, and subroutine is a route definition (AKA "route") ๐Ÿท๏ธ
  • Only the subroutine reference (sub {...}) is the route handler ๐Ÿ› ๏ธ
  • The route definition and the route handler collectively are the route โœจ

Each route requires a defintion and handler. The route needs to either return a string or Perl data structure to be rendered for the client. We'll learn more about rendering data structures later in our guide to Danceyland; for now, we're going to focus on returning strings, which will be rendered as HTML ๐Ÿ’…

What if we want a single park location to respond to multiple request types? ๐Ÿค”

Route definitions can use any to match all, or a specified list of HTTP methods.

The following will match any HTTP request to the path /visitor-center:

any '/visitor-center' => sub {
    # Write code to do something at the visitor center!
}

The following will match GET or POST requests to /visitor-center:

any ['get', 'post'] => '/visitor-center' => sub {
    # Write code to do something at the visitor center!
};

๐Ÿ”— URI Generation ๐Ÿ”—

Dancer2 can generate URIs using the uri_for and uri_for_route keywords. Letting Dancer2 generate URIs helps ensure consistency, and reduces the amount of maintenance needed by making sure URIs are always up to date as the application evolves over time. Basically it does the upkeep so you don't have to ๐Ÿงš

uri_for โœจ

The uri_for keyword is used to generate a URI for a given path within your application, including query parameters. It's especially useful when you want to construct URLs dynamically inside your routes.

Example: Generating a URI in Danceyland

get '/ride/:name' => sub {
    my $ride_name = route_parameters->get('name');
    return 'Enjoy the ride at ' . uri_for("/ride/$ride_name");
};

In this example, uri_for generates a full URI for the ride name in Danceyland ๐ŸŽก

uri_for_route โœจ

The uri_for_route keyword creates a URL for a named route. In Danceyland, it can be used to generate a URL to any part of the park that has a named route.

For more information, see uri_for_route in Dancer2::Manual::Keywords.

Example 1: Basic Usage

get 'films' => '/cinemaland/film-gallery' => sub {
    return "See the films at " . uri_for_route('films');
};

In this example, the route is named "animals", and uri_for_route generates a URL pointing to it. ๐Ÿพ

Example 2: Using Route Parameters

get 'ride' => '/ride/:name' => sub {
    my $ride_name = route_parameters->get('name');
    return "Ride details: " . uri_for_route('ride', { name => $ride_name });
};

This example uses uri_for_route to generate a URL that includes the named route parameter name.

Example 3: Including Query Parameters

get 'search' => '/search' => sub {
    return uri_for_route('search', { q => 'roller coaster' });
};

get '/search' => sub {
    my $query = query_parameters->get('q');
    return "Search results for: $query";
};

In this example, uri_for_route generates a URL for the named route "search" with the query parameter q set to roller coaster ๐Ÿ”

๐Ÿ“ฅ Parameter Handling ๐Ÿ“ฅ

In Danceyland, visitors will often need to communicate with park staff. Similarly, your apps will need to take in information from application users via parameters. Dancer2 provides several methods for handling different types of parameters ๐Ÿซถ

๐Ÿงฐ Keywords for working with parameters ๐Ÿงฐ

The param and params keywords are legacy methods to access request parameters. While still functional, they are not recommended for new applications. Instead, you should use the more specific keywords like route_parameters, query_parameters, and body_parameters for clarity and precision ๐Ÿ’…

Example: Using param (Not Recommended)

get '/submit' => sub {
    # Please use query_parameters() shown below
    my $name = param('name');
    return "Submitted name: $name";
};

Example: Using params (Not Recommended)

get '/all-params' => sub {
    # Pleaase use query_parameters() shown below
    my %all_params = params;
    return "All parameters: " . join(', ', %all_params);
};

param and params are included here for completeness but are not the preferred methods for handling parameters in Danceyland ๐Ÿ™ˆ

body_parameters ๐Ÿ’Œ

This keyword retrieves parameters from the body of the request, typically used in POST requests.

Example of body_parameters with multiple values:

post '/submit' => sub {
    my $name  = body_parameters->get('name');
    my $email = body_parameters->get('email');
    return "Submitted name: $name, email: $email";
};

query_parameters ๐Ÿ”Ž

This keyword retrieves parameters from the query string of the request URL:

# Matches URL: /search?q=rides&cat=thrill
get '/search' => sub {
    my $query    = query_parameters->get('q');
    my $category = query_parameters->get('cat');
    return "Search query: $query in category: $category";
};

The above route would match the URL /search?q=rides&cat=thrill.

route_parameters ๐Ÿ›ฃ๏ธ

This keyword retrieves named parameters from the route declaration:

# Matches URL: /user/123
get '/user/:id' => sub {
    my $user_id = route_parameters->get('id');
    return "User ID: $user_id";
};

get_all

okay so get_all is the โœจcollectorโœจ of the family โ€” it works for all of the above parameter-fetching keywords. Got a param that might hold more than one value? get_all hands you back an array reference holding every selected value, even when there's literally only a single value coming back. ๐Ÿซถ

Example of get_all:

# Matches URL: /all-params?name=John&age=30
get '/all-params' => sub {
    my $params = query_parameters->get_all();
    return "All query parameters: " . join(', ', %$params);
};

Named Route Parameters

Named route parameters let you snag specific chunks of the URL and actually use them in your route handler, bestie:

# Matches URL: /ride/roller-coaster
get '/ride/:name' => sub {
    my $ride_name = route_parameters->get('name');
    return "Welcome to the $ride_name ride!";
};

You grab named route parameters with the route_parameters keyword. periodt. ๐Ÿ’…

Wildcard Route Parameters

Wildcard route parameters let you capture arbitrary parts of the URL. There are two types: splat and megasplat. splat is the single asterisk (*), and megasplat is the double asterisk (**). ๐ŸŒˆ

Examples of wildcard route parameters include:

  • splat โ€” captures one segment of the URL
# Matches URL: /files/document.txt
get '/files/*' => sub {
    my ($file) = splat;
    return "You requested the file: $file";
};
  • megasplat โ€” captures multiple segments of the URL
# Matches URL: /files/documents/reports/2023/summary.txt
get '/files/**' => sub {
    my @files = splat;
    return "You requested the files: " . join(', ', @files);
};

Combining named and wildcard parameters

You can combine named and wildcard parameters in your routes to capture both the specific bits and the arbitrary bits of the URL. it's giving โœจrangeโœจ.

Example combining named and wildcard parameters:

# Matches URL: /user/123/files/documents/reports/2023/summary.txt
get '/user/:id/files/**' => sub {
    my $user_id = route_parameters->get('id');
    my @files = splat;
    return "User ID: $user_id requested the files: " . join(', ', @files);
};

Named parameters with type constraints

Type constraints let you enforce specific types for named parameters, so the params actually meet certain criteria instead of showing up however they want ๐Ÿ˜ญ.

Dancer2 natively supports named parameters with type constraints โ€” no external plugins needed. Here's how to declare and use type constraints for named route parameters in Dancer2:

use Dancer2;

get '/ride/:id[Int]' => sub {
    my $id = route_parameters->get('id');
    return "Ride ID: $id";
};

get '/guest/:name[Str]' => sub {
    my $name = route_parameters->get('name');
    return "Guest Name: $name";
};

MyApp->to_app();
  • Int โ€” this constraint makes sure the id parameter must be an integer.
  • Str โ€” this one makes sure the name parameter must be a string.
Note: for more complex parameter types, the Dancer2::Plugin::ParamTypes module brings additional constraints and validation for all parameter types. iykyk ๐Ÿงš

Wildcard Parameters with Type Constraints

You can also enforce type constraints on wildcard parameters, bestie:

# Matches URL: /images/photo.jpg
get '/images/*.*[ArrayRef[Str]]' => sub {
    my ($filename, $extension) = splat;
    return "Filename: $filename, Extension: $extension";
};

# Matches URL: /documents/folder/subfolder/file.txt
get '/documents/**[ArrayRef[Str]]' => sub {
    my @path = splat;
    return "Document path: " . join('/', @path);
};

Regex Route Matching

Regex route matching lets you define routes with regular expressions, so you get way more flexibility in matching URLs ๐Ÿฆ‹:

# Matches URL: /product/12345
get qr{/product/(\d+)} => sub {
    my ($product_id) = splat;
    return "Product ID: $product_id";
};

# Matches URL: /category/electronics
get qr{/category/(\w+)} => sub {
    my ($category_name) = splat;
    return "Category: $category_name";
};

It is also possible to use named captures in regular expressions:

# Matches URL: /product/12345
get qr{/product/(?<product_id>\d+)} => sub {
    my $product_id = captures->{'product_id'};
    return "Product ID: $product_id";
};

Combining Examples

# Matches URL: /item/42/specifications
get '/item/:id[Int]/*[ArrayRef[Str]]' => sub {
    my $item_id = route_parameters->get('id');
    my ($detail) = splat;
    return "Item ID: $item_id, Detail: $detail";
};

# Matches URL: /archive/2023/07/10
get qr{/archive/(\d{4})/(\d{2})/(\d{2})} => sub {
    my ($year, $month, $day) = splat;
    return "Archive for: $year-$month-$day";
};

Organizing routes and growing your app using prefix

The prefix DSL keyword is your new bestie for grouping related routes together ๐Ÿ’–. For example, you can organize all the routes of a given section in Danceyland with the prefix keyword like this:

package MyApp;
use Dancer2;

# Prefix for Cinemaland
prefix '/cinemaland' => sub {
    get '/film-gallery' => sub {
        return "Welcome to Cinemaland! Here you can learn about famous movies.";
    };

    get '/movie-schedule' => sub {
        return "Movie Schedule: 10 AM and 4 PM.";
    };
};

# Prefix for Actionland
prefix '/actionland' => sub {
    get '/thrill-rides' => sub {
        return "Welcome to Actionland! Enjoy the thrill rides.";
    };

    get '/roller-coaster' => sub {
        return "The best roller coaster in the park!";
    };
};

MyApp->to_app();

This example organizes routes by grouping all the cinema-related activities under /cinemaland and all the rides under /actionland. ๐ŸŽข


Controlling the flow with forward, redirect, and pass

These DSL keywords in Dancer2 let you manage the flow of actions inside your routes. Here's how they work in Danceyland ๐Ÿ‘‘

forward

forward lets you forward the current request to another route handler, as if it were redirected, but without sending a new HTTP request. Put another way: a different route than the one requested gets processed, but the address in the user's browser bar stays exactly the same. no cap.

Example

get '/guest-services' => sub {
    forward '/info';  # Forwards to /info
};

get '/info' => sub {
    return "Welcome to Guest Services. How can we help you today?";
};

In this example, when a visitor goes to /guest-services, they get forwarded to the /info route internally โ€” but the visitor's browser still shows they're in /guest-services. sneaky โœจ

redirect

redirect sends an HTTP redirect to the client, telling their browser to make a new request to a different URL. At the end of the request, the user's browser will show a different URL than the one they originally navigated to.

Example

get '/old-roller-coaster' => sub {
    redirect '/new-roller-coaster';
};

get '/new-roller-coaster' => sub {
    return "Welcome to the new and improved roller coaster!";
};

When a visitor requests /old-roller-coaster, they get redirected to /new-roller-coaster. The browser's URL will now show /new-roller-coaster. glow up ๐Ÿ’…

pass

pass tells Dancer2 to skip the current route and keep looking for the next matching route.

Example

get '/vip-area' => sub {
    if (!session('is_vip')) {
        pass;  # Skip to the next matching route if the user is not VIP
    }
    return "Welcome to the VIP area!";
};

get '/vip-area' => sub {
    return "Access Denied. This area is for VIPs only.";
};

In this example, if the session doesn't indicate the user is a VIP, the request skips to the next matching route, which denies access. ๐Ÿšซ


โœจ Templates: Displaying Information, and More! โœจ

Templates in a web app help you present information in a structured and visually appealing way โ€” much like maps, schedules, and banners in a theme park. ๐ŸŽ€

Why do we use templates?

Templates help separate your display logic from your programming logic. The same code that sends JSON to satisfy an API quest could also be used to display an HTML page containing park information to a user. If the code needed to display HTML is all tangled up with the code that gathers the data, then the code needed to just produce JSON becomes unnecessarily complicated. Templates take the HTML out of your Perl code. periodt. ๐Ÿงš

Views

In Dancer2, views refer to the template files that define the structure of the content presented to the users. Views are typically HTML files with embedded placeholders that get dynamically filled with data when rendered.

How Views Work

Suppose you have a template file called views/ride.tt:

<h1><% ride_name %> Ride</h1>
<p>Enjoy the thrilling <% ride_name %> ride at Danceyland!</p>

You can use this view in your route handler:

get '/ride/:name' => sub {
    my $ride_name = route_parameters->get('name');
    template 'ride' => { ride_name => $ride_name };
};

Example: displaying a welcome message on a pretty banner

get '/' => sub {
    template 'welcome', { message => "Welcome to Danceyland!" };
};

In this example, the / route uses a template to display a welcome message.

Example: displaying the park map

get '/map' => sub {
    template 'map', { attractions => \@attractions };
};

Example: using templates to display various information

get '/info' => sub {
    template 'info', {
        title   => "Park Information",
        content => "Here you can find all the information about Danceyland.",
        hours   => "Open from 10 AM to 8 PM",
        contact => "Call us at 123-456-7890",
    };
};
  • The / route uses a template to display a welcome message. ๐Ÿ‘‹
  • The /map route uses a template to display the park's map. ๐Ÿ—บ๏ธ
  • The /info route uses a template to display various pieces of information about the park. ๐Ÿ“‹

New Keywords

template

The "template" keyword renders a template file with the provided data. It takes two parameters: the template name and a hash reference of the data to pass to the template. Easy peasy, bestie. โœจ

template 'template_name', { key1 => 'value1', key2 => 'value2' };

Layouts ๐Ÿ’–

Layouts in Dancer2 allow you to wrap views with a common structure, such as a header, footer, or navigation bar, that remains consistent across Danceyland. A layout is just another template that wraps around your page content; it allows you to maintain a consistent look and feel across multiple pages while rendering the dynamic content of each page within the layout. It's giving "reusable vibe" and we're obsessed. ๐ŸŽ€


Example: Implementing Layouts in Danceyland โœจ

Consider Danceyland having a consistent navigation bar across all sections of the site. You can use a layout for this.

# views/layouts/main.tt
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Danceyland</title>
</head>
<body>
    <header>
        <h1>Welcome to Danceyland</h1>
        <nav>
            <a href="/">Home</a>
            <a href="/cinemaland/film-gallery">Cinemaland</a>
            <a href="/actionland/thrill-rides">Actionland</a>
        </nav>
    </header>

    <section>
        <% content %>  <!-- This is where the view content gets inserted -->
    </section>

    <footer>
        <p>ยฉ 2024 Danceyland</p>
    </footer>
</body>
</html>

"content" is a special variable for layouts; it is replaced by the output from the "template" keyword. Iconic behavior, honestly. ๐Ÿ’…


Route Implementation Using the Layout ๐ŸŽข

If you have a separate layouts for park employees and park guests, you can explicitly choose which layout should be used to wrap page content:

get '/food-court/burger-stand' => sub {
    template items => {
        foods => \@foods,
        drinks => \@drinks,
    }, { layout => 'main' };
};

get '/cinemaland/change-film-reels' => sub {
    template films => { films => \@films }, { layout => 'employee' };
};

get '/actionland/thrill-rides' => sub {
    template rides => { rides => \@rides }, { layout => 'visitor' };
};

In this example:

  • Employees and guests both choose from the same menu, so in the food court, they should be presented with the same standard page elements and options.
  • Guests of Cinemaland aren't required to change the film reels between showings, but park employees are. By specifying a layout of "employee.tt", they will get a special look and set of options appropriate to park workers. The route renders a template ("films.tt") within that layout.
  • Park visitors interact with rides differently than employees. By displaying the "rides.tt" template within the "visitor.tt" layout, we can help guests board and get off rides with consistent messaging and instructions.

This helps maintain consistency across different pages in Danceyland by reusing the layout and changing only the dynamic content for each route. No cap, that's the whole point. ๐Ÿซถ


Default Layout ๐ŸŒธ

When no layout is explicitly specified in a route, Dancer2 will use the "main" layout as the default:

get '/home' => sub {
    template 'homepage';
};

In this case, Dancer2 renders "homepage.tt" within the default layout "main.tt". Default? More like main character energy. ๐Ÿ‘‘


Disabling Layouts ๐Ÿ™ˆ

If you want to render a view without a layout, you can pass { layout => undef } to the "template" keyword:

get '/payment-options' => sub {
    template 'payment-options', { }, { layout => undef };
};

This route renders the "payment-options.tt" template without using the default or any specified layout. Sometimes you just gotta be free, yk? ๐Ÿฆ‹


When should I use a layout, and when shouldn't I? ๐Ÿค”

There are three different ways to return data from a Dancer2 app:

  • Returning HTML content that represents the entire content of the page (and allowing the browser to render it)
  • HTML content for only a portion of the page (using JS to insert that HTML dynamically in the page, and the browser then takes over rendering it)
  • JSON/XML/etc. content that the JS decides what to do with, possibly rendering some front-end template and updating the DOM nodes

For option 1, you would use a layout to ensure a full HTML page is returned. For option 2, you would disable the layout. In option 3, you would serialize a data structure returned from a route.

The benefit of returning a part of a HTML page (option 2) is when:

  • You don't want to render the entire page in the backend
  • You don't want to fetch all the data for the entire page (imagine menus, headers, etc.)
  • You prefer to inject the HTML directly to an element, reducing front-end complexity

If you are building interfaces with toolkits like htmx (https://htmx.org/) or Turbo (https://turbo.hotwired.dev/), it's important to only return a partial page. Important! Don't skip this. โš ๏ธ


Default template variables ๐Ÿงธ

  • perl_version โ€” The version of Perl that's running Danceyland. This aligns with Perl's special $^V variable. Basically Perl's little name tag.
  • dancer_version โ€” The current version of Dancer2. Similar to Dancer2->VERSION. You already know.
  • settings โ€” This is a hash containing the configuration of Danceyland. All the config tea.
  • request โ€” This represents the user's current request. Calling request returns a Dancer2::Core::Request object. Request object, periodt.
  • params โ€” params provides a hash reference of all parameters passed to Danceyland. All the params, all the time.
  • vars โ€” This is the list of variables for this request. It is the same thing you would get if you used the vars keyword in the application itself. We'll learn more about vars in the "Configuration" section. Vars are your friends.
  • session โ€” This variable contains all information in the user's session, should one exist. We'll talk more about sessions in the next section. Session stuff? Later, bestie.

These are your default besties in every template. Love that for you. ๐Ÿ’–


Configuring the Template Engine โš™๏ธ

You can configure the template engine in Dancer2 to customize aspects such as the open and closing tags used for placeholders.

Changing Open and Closing Tags ๐Ÿท๏ธ

In your configuration file (e.g., "config.yml"), you can set the "start_tag" and "stop_tag" options:

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

Dancer2 defaults placeholder tags to <% %>. The above configuration restores the Template toolkit defaults of [% %]. Your template variables will now look like this:

<h1>[% title %]</h1>
<p>Welcome to Danceyland!</p>

Most template engines support this functionality, but some (such as Dancer2::Template::Tiny) do not. Noted! Not all engines are flexible like that. ๐Ÿ’…


Encoding in Templates ๐ŸŒˆ

Dancer2 supports encoding for templates to ensure that the content is correctly displayed, especially when dealing with non-ASCII characters:

template: "template_toolkit"
engines:
  template:
    template_toolkit:
      encoding: 'utf-8'

This configuration ensures that the templates are processed using UTF-8 encoding. This is particularly important if your templates contain special characters, such as international text or symbols. Global vibes only. ๐ŸŒ

New Keywords ๐Ÿ”‘

  • engine

At the heart of every attraction in Danceyland lies a powerful engine that brings it to life. Similarly, in Dancer2, the "engine" keyword provides access to the current engine object, whether it's the template engine rendering the views or the logger keeping track of events:

my $template_engine = engine 'template';

Engine go brrr. ๐Ÿš‚


Comparison of Supported Template Engines ๐Ÿ†

Dancer2 supports various template engines to give you flexibility in how you render views. Here's a brief comparison of the available engines:

  • Dancer2::Template::TemplateToolkit (Template Toolkit, TT) โ€” A powerful and flexible templating system with extensive features like loops, conditions, macros, and plugins. Suitable for complex templating needs. It's giving "full-featured queen".
  • Dancer2::Template::Mason (Mason) โ€” A component-based templating system that allows embedding Perl code within templates. Ideal for complex web applications that require reusable components. Perl inside templates? Iconic.
  • Dancer2::Template::Xslate (Xslate) โ€” A high-performance templating engine that offers a mix of syntax styles. Great for applications requiring speed and flexibility. Speed? We love to see it.
  • Dancer2::Template::TemplateFlute (Flute) โ€” A template engine focused on separating logic from presentation. Templates contain no logic and are designer-friendly. Designer-friendly? Slay.
  • Dancer2::Template::Tiny (Template::Tiny) โ€” A minimalistic template engine with limited features. Suitable for small projects or when you need a simple, lightweight option. Small and cute, like a pocket-sized template engine.
  • Dancer2::Template::Simple โ€” A basic and straightforward templating engine. Not recommended unless you are migrating applications from Dancer 1 built with Dancer::Template::Simple. Not recommended, bestie โ€” only for migration vibes.
  • Dancer2::Template::Mustache (Mustache:https://mustache.github.io/, Dancer2::Template::Mustache) โ€” A logic-less template engine with a strong emphasis on separation of logic and presentation. Great for consistent rendering across different languages and platforms. Logic-less? The way it just works everywhere.
  • Dancer2::Template::Handlebars (Handlebars, Text::Handlebars) โ€” An extension of Mustache with additional features like helpers and more expressive templates. Ideal for complex templates that still maintain a clean and readable structure. Mustache's glow-up era.
  • Dancer2::Template::Haml (HAML https://haml.info/, Text::Haml) โ€” A clean and concise templating system that uses indentation to define HTML elements. Perfect for those who prefer a more human-readable approach to HTML templating. Indentation-based HTML? Obsessed.

Choose a template engine based on the complexity and requirements of your Danceyland application. Slay the engine choice, bestie. โœจ

๐ŸŽŸ๏ธ Sessions: Multi-Day Passes

Okay so sessions are literally the multi-day pass of the web, bestie. They let visitors come back to the park over and over without buying a whole new ticket every single time. Iconic.

๐Ÿง  The importance of state

HTTP and HTTPS are stateless protocols. That means every request from a client to a server is independent. The server handles each request, sends a response, and then... doesn't remember anything about past interactions. So each new request has to bring all the necessary info with it, because the server keeps zero "state" between requests โ€” and if you want state, you (the developer) have to build it. Periodt.

That whole no-memory-between-requests thing is exactly why web frameworks do session management. Sessions let servers remember what users did across multiple requests, creating continuity, aka "state." To pull this off, web frameworks often lean on cookies โ€” tiny pieces of data stored in the browser. When a client connects, the server gives them a unique session ID stored in a cookie, and the browser sends that cookie with every request. That session ID is how the server "remembers" previous interactions, making personalized and seamless web experiences possible. Slay.

Next up: how to manage sessions with Dancer2.

๐ŸŽข Managing visitor sessions

To create or update a session, use the session keyword. Sessions store data across multiple requests using a unique session ID.

set session => 'Simple';

get '/login' => sub {
    session user => 'John Doe';
    return "Welcome back, John Doe!";
};

get '/profile' => sub {
    my $user = session('user');
    return "User profile for: $user";
};
  • A session stores the user information once they log in.
  • The /login route initiates a session for the user.
  • The /profile route retrieves the user information from the session.

๐Ÿ”‘ New Keywords

  • set โ€” this keyword configures settings for your app, like setting the session engine:
set session => 'Simple';
  • session โ€” this keyword stores and retrieves session data:
session user => 'John Doe';  # Store data
my $user_1 = session('user');  # Retrieve data
my $user_2 = session->read('user');  # Also retrieves data

๐Ÿ”’ Changing the Session ID

For security reasons, you might want to change the session ID, especially after a user logs in. This helps prevent session fixation attacks (https://owasp.org/www-community/attacks/Session_fixation). Do not skip this, bestie.

get '/secure-login' => sub {
    my $username = body_parameters->get( 'username' );
    session user => $username;
    app->change_session_id;  # Change the session ID
    return "Welcome back, $username!";
};

๐Ÿ’ฅ Destroying Sessions

To log the user out and destroy the session, use app->destroy_session.

get '/logout' => sub {
    app->destroy_session;
    return "You have been logged out.";
};

โš™๏ธ Configuring the Session Engine

Dancer2 supports a bunch of session engines that decide how and where session data gets stored. You can configure the session engine in the config.yml file:

session: "Simple"
session_options:
  cookie_name: "danceyland_session"
  expires: 3600

This config uses the "Simple" session engine, stores session data in memory, and sets the session to expire in one hour. Easy.

๐Ÿ—„๏ธ Session Storage Backends

Dancer2 supports multiple session storage backends, so you can choose how and where to store session data.

  • Dancer2::Session::Simple โ€” stores session data in memory. Suitable for development and testing, but not recommended for production because it's non-persistent.
  • Dancer2::Session::YAML โ€” stores session data in a YAML file. Useful for development and debugging, but not well suited for production.
  • Dancer2::Session::JSON โ€” stores session data in a JSON file. Similar restrictions to the YAML session engine.
  • Dancer2::Session::Cookie โ€” stores session data within the user's browser as a cookie. Data is not stored server-side. Good for lightweight sessions.
  • Dancer2::Session::PSGI โ€” leverages PSGI's built-in session management. Useful if you want to delegate session handling to the PSGI server, or if you're sharing session data with non-Dancer2 applications.
  • Dancer2::Session::DBI โ€” stores session data in a database using DBI. Ideal for larger applications that require a robust and scalable storage solution.
  • Dancer2::Session::Redis โ€” stores session data in a Redis database. Suitable for high-performance, distributed, and scalable session storage.
  • Dancer2::Session::Sereal โ€” uses Sereal for fast, compact session serialization. Suitable for high-performance applications requiring efficient serialization.
  • Dancer2::Session::CHI โ€” uses the CHI caching framework for session storage. Flexible and supports various caching backends like memory, file, and databases.
  • Dancer2::Session::Memcached โ€” stores session data in a Memcached server. Suitable for distributed environments requiring quick access to session data.
  • Dancer2::Session::MongoDB โ€” stores session data in a MongoDB database. Ideal for applications requiring a NoSQL storage solution.

Choose a session backend based on the complexity and storage needs of your Danceyland application. Iykyk.

๐Ÿช Cookies

Cookies are small pieces of data stored on the client's browser โ€” not just the goodies you can buy in Danceyland. They're often used to keep track of session information and user preferences.

Sessions in Dancer2 use cookies to store the session ID on the client's browser.

๐Ÿช Setting and Updating Cookies

You can set a cookie using the cookie keyword. To update a cookie, just set it again with a new value:

get '/set-cookie' => sub {
    cookie 'visitor' => 'regular';
    return "Cookie has been set!";
};

This route sets a cookie named "visitor" with the value "regular".

๐Ÿช Retrieving Cookies

To retrieve a cookie, use the cookie keyword with the cookie's name:

get '/get-cookie' => sub {
    my $visitor_type = cookie('visitor');
    return "Visitor type: $visitor_type";
};

This route retrieves the value of the "visitor" cookie.

To retrieve all cookies, use the cookies keyword:

get '/check-cookies' => sub {
    my $cookies = cookies;
    for my $name ( keys $cookies->%* ) {
        debug "You have a cookie named $name";
        # Do other stuff...
    }
}

Cookie values can be multi-valued. cookie('name') returns the first value in scalar context and all values in list context.

my $visitor  = cookie('visitor'); # single value
my @visitors = cookie('visitor'); # multiple values (list context)

However, you can also call the methods value (for either scalar/list contexts), or values for list only context:

my $cookie   = cookies->{'name'};
my $visitor  = $cookie->value();
my @visitors = $cookies->values();

# this also works, similar to the DSL keyword 'cookie'
my @visitors = $cookie->value();

๐Ÿช Deleting Cookies

To delete a cookie, set it with an expiration time in the past:

get '/delete-cookie' => sub {
    cookie 'visitor' => 'expired', expires => '-1d';
    return "Cookie has been deleted.";
};

This route deletes the "visitor" cookie by setting its expiration date to a past date.

๐ŸŽข Error Handling: Managing Ride Breakdowns and Other Park Issues

Just like managing ride breakdowns or out-of-stock concessions in a theme park, error handling in a web application is all about dealing with unexpected issues gracefully. Vibe check: chaos happens.

๐Ÿ˜ญ Why should we fail gracefully?

Regardless if a problem is one we can control or not, not handling a problem

Whether or not a problem is one we can predict, handling errors in a poor or disruptive way can really ruin a visitor's day at the park. It may not be as rude as the pretzel vendor that yells at you for not leaving a tip, but it can certainly sour a vistor for future visits.

Errors happen. Handling errors in a predictable, friendly manner can make all the difference. Give visitors a friendly message that explains what went wrong, and if you can still provide them with some limited park functionality, then do so. Bestie energy only.

500 - the error of last resort

When a 500 error occurs, either it means the park operators didn't think to check for a particular error, or something absolutely unpredictable happened (a gust of wind knocked a tree branch across a park walkway).

When issuing a 500 error, try not to give away too much information (such as a stack trace) so as to not give a potential attacker too much information. No leaking stack traces, okay?

Try to catch as many things as you can. Sending a 500 error isn't the end of park operations, but they should show up sparingly, at worst.

๐ŸŽข Handling Errors When a Ride is Closed

get '/roller-coaster' => sub {
    status 'service_unavailable';
    return "Sorry, the roller coaster is currently closed for maintenance.";
};

This route catches the request to the roller coaster when it's closed and returns a friendly error message.

๐Ÿ“„ Error Pages

You can define custom error pages to provide a friendly message to your users when something goes wrong.

To create a custom error page, place a template in the views directory named after the error code, like views/404.tt:

<h1>Oops! Page Not Found</h1>
<p>The page you are looking for does not exist in Danceyland.</p>

This custom page will be displayed whenever a 404 error occurs.

๐Ÿ”‘ New Keywords

  • status โ€” sets the HTTP status code that is returned by the response from Dancer2. It can be specified as either a number (status 404), or its lower-case name (status 'not_found').
  • halt โ€” immediately stops the processing of the current route and sends a response back to the client:
get '/restricted' => sub {
    halt "Access denied!" unless session('is_admin');
    return "Welcome to the restricted area.";
};

In this example, if the visitor is not an admin, halt stops execution and returns "Access denied!".

  • send_error โ€” sends an error response with a specific status code:
get '/data' => sub {
    my $data = fetch_data();
    send_error "Data not found", 404 unless $data;
    return $data;
};

This example sends a 404 error response if the data is not found.

๐Ÿฆ‹ Static Files and File Uploads ๐Ÿฆ‹

Okay so bestie, in Danceyland we don't just offer thrilling rides; we also serve files! Whether youโ€™re providing a downloadable park map or processing guest photo uploads, Dancer2 has you covered. ๐Ÿ’– No cap.

๐ŸŒธ Serving Static Files from Dancer2 ๐ŸŒธ

Sometimes you want to make certain information (in the form of files) available to visitors without asking them to stand in line. Dancer2 makes serving static files easy. It's giving convenience.

โœจ From a Directory โœจ

By default, Dancer2 serves static files from the public directory in your app. Just place your files (like images or PDFs) in the public folder, and theyโ€™ll be accessible at /static/path:

# Access the file public/images/logo.png via
http://danceyland.local/static/images/logo.png

๐Ÿ’… Sending Static Files from a Route Handler ๐Ÿ’…

You can also serve static files programmatically from a route handler using send_file:

get '/download-map' => sub {
    send_file 'park-map.pdf', content_type => 'application/pdf';
};

Using send_file immediately exits the current route. ๐Ÿ˜ญ Like, it's done, no more code runs in that route. Periodt.

๐ŸŽ€ When Might You Not Want to Serve Static Files? ๐ŸŽ€

While serving static files is convenient, there are times when you may not want to do it directly:

  • You want to offload static content delivery to a CDN (Content Delivery Network) for performance.
  • You need fine-grained access control on your files.
  • Your static content is large or frequently changing, and you'd prefer a more scalable solution.

๐Ÿซถ How to Disable Static File Serving ๐Ÿซถ

If you'd prefer not to serve static files from your Dancer2 app, you can disable it in your config.yml file:

static: 0

Now, all file requests will be routed through your handlers or passed to another service. Lowkey handy.

๐Ÿงš Using send_file to Deliver Files from Memory ๐Ÿงš

Youโ€™re not limited to files on disk! You can use send_file to deliver files created or stored in memory:

get '/dynamic-file' => sub {
    my $data = "This is a dynamically generated file.";
    return send_file(\$data, content_type => 'text/plain', filename => 'dynamic.txt');
};

In this example, we generate a file on-the-fly and send it back to the user. โœจ Iconic.

๐ŸŒˆ Handling File Uploads ๐ŸŒˆ

If guests are submitting photos or other files at Danceyland, you'll need to handle file uploads. Dancer2 makes it easy with the upload keyword:

post '/upload-photo' => sub {
    my $upload = upload('photo');
    return "No file uploaded" unless $upload;

    my $filename = $upload->filename;
    my $filehandle = $upload->file_handle;
    open my $out, '>', "/uploads/$filename" or die "Failed to open: $!";
    while (<$filehandle>) {
        print $out $_;
    }
    close $out;

    return "Uploaded $filename successfully!";
};

โญ Working with Paths and Directories โญ

Dancer2 provides keywords for navigating paths and directories, similar to navigating Danceyland. Main character energy.

path

Navigating through Danceyland requires clear paths and an understanding of the surroundings. In Dancer2, the path keyword allows you to create a path from several different directories:

use Dancer2;

get '/attraction/:name' => sub {
    my $current_path = path( '/the', 'road', 'less', 'traveled' );
    return "You're visiting: $current_path";
};

dirname

Given a path through Danceyland, like above, dirname will tell you the directory you are located in:

use Dancer2;

get '/resource' => sub {
    my $file_path = '/path/to/danceyland/config.yml';
    my $directory = dirname($file_path);
    return "Config file is located in: $directory";
};

๐Ÿ’– Keywords Covered ๐Ÿ’–

  • send_file โ€” Used to send a file, either from disk or memory, to the client.
  • upload โ€” Used to retrieve an uploaded file.

๐Ÿ‘‘ Configuration, Startup, Initialization, Versioning ๐Ÿ‘‘

Danceyland needs its rides and attractions to work just right, and so does your Dancer2 app. Configuration is key to ensuring your app runs smoothly in every environment. ๐Ÿ’ฏ Fr fr.

๐ŸŒธ How Do Environments Help Us Configure Our Applications? ๐ŸŒธ

Dancer2 uses environments (such as development or production) to customize configuration depending on where the app is running. This allows you to tweak things like logging, error reporting, or even database connections, based on your environment.

environments:
  development:
    logger: "console"
    show_errors: 1
  production:
    logger: "file"
    show_errors: 0

All of your applications will use config.yml, which is the base configuration across all of your application's environments. It is here where you will configure such things as your template engine and other extensions/settings that do not change across environments.

๐ŸŽ€ Using Environment-Specific Config Files ๐ŸŽ€

You can create environment-specific config files, like production.yml and development.yml, in the environments directory. Dancer2 will load the environment-specific file after the main config, applying those settings last.

You are not limited to the environment names used; different projects and companies will have different requirements and different environments.

Applications are not limited to YAML-based configuration files. Dancer2 supports config files in any format supported by Config::Any, including JSON and Apache-style configs.

๐Ÿ’… Config File Resolution Order ๐Ÿ’…

Dancer2 resolves configuration in the following order:

  • config.yml
  • Environment-specific config (e.g., environments/development.yml)
  • Settings in the environment or explicitly set during runtime (using the set keyword)

โœจ Accessing Configuration Information โœจ

You can access configuration values in your app using the config keyword.

Example:

get '/info' => sub {
    my $app_name = config->{'appname'};
    return "Welcome to $app_name!";
};

๐Ÿซถ Using set Explicitly ๐Ÿซถ

You can explicitly set configuration values using the set keyword.

Example:

set session => 'Simple';
set logger => 'console';

๐ŸŒˆ Plugin Configuration ๐ŸŒˆ

You can configure plugins in your config.yml file under the plugins section.

Example:

plugins:
  Database:
    driver: 'SQLite'
    database: 'danceyland.db'

โญ Startup and Initialization โญ

Some keywords can help prepare Danceyland to start operations in the morning, while others offer you insider access to critical areas of the park. Let's take a look at what some of these are,

Accessing the Application Object

Sometimes, you really have to get your hands dirty and go behind the scenes of things at Danceyland. The app keyword gives you access to the application object (of type Dancer2::Core::App) to change configuration or perform certain functions. Another keyword, dancer_app, also lets you access the application object.

Running Code at Startup

The prepare_app keyword takes a coderef that will be run once at the start of your application. If there is any one-time setup that needs to be performed (like starting up another service, connecting to a service, initializing a cache, etc.), prepare_app will give you a place to do this.

For example:

prepare_app {
    debug "Starting our morning prep work";
    $rides->power_on();
    $vendors->prep_food();
    debug "Open for business!";
};

Starting the Application

The traditional way of kicking things off at Danceyland was to, well, dance!

use Dancer2;

get '/' => sub {
    return 'Welcome to Danceyland!';
};

dance;

As time passed and things changed, dancing wasn't always the right way to start the day. to_app became the better and preferred way to start our Dancer2 applications. psgi_app may also be used.

Versioning and Compatibility

As Danceyland introduces new attractions, it's essential to ensure they align with the park's current standards. In the realm of Dancer2, you can use dancer_version to retrieve the full version number of your framework, aiding in maintaining compatibility and leveraging the latest features:

use Dancer2;
return "Running on Dancer2 version " . dancer_version;

dancer_major_version provides the major version of Dancer2, helping in ensuring compatibility with plugins or external components:

use Dancer2;
return "Running on Dancer2 major version " . dancer_major_version;

๐Ÿ‘‘ New Keywords ๐Ÿ‘‘

  • app โ€” Returns the application object. App object is a Dancer2::Core::App.
  • config โ€” Retrieves configuration values.
  • dance โ€” Starts the application. Not recommended.
  • dancer_app โ€” Returns the application object. Synonym for app.
  • dancer_major_version โ€” Returns the major version of Dancer2.
  • dancer_version โ€” Returns the full version number of Dancer2.
  • prepare_app โ€” Runs a coderef at the start of your application only.
  • psgi_app โ€” Synonym for to_app.
  • set โ€” Explicitly sets configuration options.
  • setting โ€” Returns the value of the specified config setting.
  • to_app โ€” Returns a coderef to your application, to be started by a Plack server.
  • var โ€” Set and retrieve a temporary named value within your application.
  • vars โ€” Returns a hashref or all temporary variables/values in your application.

๐ŸŽข Logging ๐ŸŽข

Okay bestie, you know how the park has like a million monitors watching every roller coaster track in real time? Logging is exactly that, but for your Dancer2 app. It's the group chat where your code tells you what's going on. ๐Ÿ’–


๐Ÿ’… Why Should We Log? ๐Ÿ’…

Logging is lowkey essential for tracking errors, performance, and just the general โœจ activity โœจ happening in your app. It's how you figure out what's going right (and, ngl, what's going very wrong) in Danceyland.

๐Ÿงš What Types of Things Might We Want to Log?

  • Errors (e.g., a ride breaking down ๐Ÿ˜ญ)
  • Important events (e.g., a new guest registering ๐ŸŽ€)
  • Debug information (e.g., the details of a request ๐Ÿ”)

๐Ÿ› ๏ธ Configuring Logging in Your Dancer2 App

You configure your logging in your config.yml file, like so:

logger: "console"
log: "debug"

This config sends your logs straight to the console and logs everything at the debug level or higher. Maximum chaos, maximum visibility. ๐Ÿ‘‘


๐Ÿ—ฃ๏ธ Logging Your Own Messages

You can drop your own messages into the log using the logging keywords, no cap:

debug "This is a debug message.";
info "A new user has registered.";
warning "The ride is slowing down!";
error "The roller coaster broke down!";

Wanna know more about extending the logger or building your own custom loggers from scratch? Go check out the Extending section. It's giving DIY icon energy. ๐Ÿซถ


โญ Keywords Covered โญ

  • debug โ€” Logs a debug-level message. The "just vibing, noting everything" tier.
  • info โ€” Logs an info-level message. Normal stuff, all good, love that for you.
  • warning โ€” Logs a warning-level message. ๐Ÿ‘€ Something's sus.
  • error โ€” Logs an error-level message. ๐Ÿšจ It broke. It's broken. It has broken.
  • log โ€” Logs a message at the log level you specify:
log( debug => 'This is a debug message' );

๐Ÿงช Testing ๐Ÿงช

Testing is everything to make sure Danceyland's attractions are safe and fun โ€” and fr fr, the exact same goes for your Dancer2 app. No vibes-only deployments, bestie. ๐Ÿ™ˆ


๐ŸŒธ Why Test? ๐ŸŒธ

Testing helps you catch bugs before your guests (aka your users) find them, which is so much less embarrassing. It makes sure your app does what it's supposed to, even when things get complicated. And it protects you from accidentally breaking stuff that already worked when you go in and start changing things. ๐Ÿ’…


๐Ÿ”ฅ Example: Using Plack::Test

Plack::Test lets you test how your app behaves without spinning up a whole web server. So efficient, so slay:

use Plack::Test;
use HTTP::Request::Common;
use MyApp;

test_psgi app => MyApp->to_app, client => sub {
    my $cb = shift;
    my $res = $cb->(GET "/");
    like $res->content, qr/Welcome to Danceyland/, "Home page works!";
};

๐Ÿฆ‹ Example: Using Test::WWW::Mechanize::PSGI

Test::WWW::Mechanize::PSGI hands you a way more user-friendly interface for testing your app, which makes simulating actual interactions so much easier:

use Test::WWW::Mechanize::PSGI;
use MyApp;

my $mech = Test::WWW::Mechanize::PSGI->new( app => MyApp->to_app );
$mech->get_ok('/');
$mech->content_contains('Welcome to Danceyland');

๐Ÿ“– More Information

Want the full deep dive on testing in Dancer2? Go read Dancer2::Manual::Testing. It's the whole syllabus. โœจ


๐ŸŽก Plugins ๐ŸŽก

Danceyland is packed with rides and attractions, but sometimes you need to add something extra โ€” like a lil popcorn stand or a souvenir shop. That's literally what plugins are for, bestie. ๐Ÿ“


๐Ÿงฉ What Are Plugins?

Plugins in Dancer2 are reusable modules that extend your app's functionality. Whether it's connecting to a database, handling authentication, or integrating with third-party services, plugins make it super easy to bolt new features onto your Dancer2 app without reinventing the wheel. Iconic behaviour. ๐Ÿ‘‘


๐Ÿ’œ Why Do We Need Plugins?

Plugins let you add functionality without reinventing the wheel. Need authentication? Validation? Plugins are there to help you, so you can focus on building the fun stuff while they handle the heavy lifting. Delegation is self-care. โœจ

Examples:

  • Dancer2::Plugin::Auth::Tiny โ€” Dancer2::Plugin::Auth::Tiny gives you a simple and easy way to protect your routes with minimal authentication logic. For example, you can restrict access to certain routes with this plugin.
  • Dancer2::Plugin::DataTransposeValidator โ€” Dancer2::Plugin::DataTransposeValidator lets you validate incoming data using Data::Transpose, giving you flexibility when you're handling forms and other user inputs.

๐Ÿ› ๏ธ How to Write a Plugin?

Writing a plugin for Dancer2 is as simple as creating a module that hooks into the Dancer2 app lifecycle. It's a fun ride, fr! For the detailed guidance on writing plugins, go check out Dancer2::Manual::Extending. ๐ŸŽข


๐Ÿ“š Complete Guide to Keywords ๐Ÿ“š

Danceyland has its own lingo that makes it easy to construct and interact with your park, and with the other developers working on the park. Dancer2 hands you a DSL (Domain-Specific Language) that makes it easy to implement, maintain, and extend your applications. You've already learned a bunch of these keywords, but there are extra ones that unlock even more functionality. ๐Ÿซถ

See the keyword guide for the complete list of keywords that Dancer2 provides. Go get your vocabulary up, bestie. ๐Ÿ’…


๐ŸŒˆ Advanced Topics ๐ŸŒˆ

Danceyland isn't just for beginners โ€” there is always more to explore. Here are some advanced features of Dancer2 that will take your app to the next level. Main character energy only. ๐Ÿ‘‘


๐ŸŽญ Composing an App with appname

In a theme park as vast as Danceyland, sometimes you need different sections, like the Magical Kingdom or Actionland. Similarly, Dancer2 lets you build separate applications, but unite them all together under a single name using the appname keyword โ€” so each section gets its own unique routing and logic. โœจ

Example: Composing multiple apps with appname into one app

Using appname, you can compose different apps inside one big web app. Each package can have its own Perl namespace but uses the same Dancer2 namespace, so all the routes are available in the one application:

# In file MagicApp.pm
package MagicApp;
use Dancer2 appname => 'MainApp';

get '/wand-shop' => sub {
    return "Welcome to the Wand Shop in the Magic Kingdom!";
};

# In file ActionApp.pm
package ActionApp;
use Dancer2 appname => 'MainApp';

get '/thrill-rides' => sub {
    return "Ready for the thrill rides in Actionland?";
};

# in MainApp.pm
package MainApp;
use Dancer2;
use MagicApp;
use ActionApp;

# In the handler
use MainApp;
MainApp->to_app();

In this example, the MagicApp package defines routes for the Magical Kingdom, and the ActionApp package defines routes for Actionland โ€” both using appname => 'MainApp'. Two parks, one wristband. ๐ŸŽ€


๐ŸŽจ Manually Adjusting MIME settings

In Danceyland, every attraction offers a unique experience, much like how different content types are handled in your application. The mime keyword gives you access to your applicaion's MIME processing object, Dancer2::Core::MIME:

use Dancer2;

get '/file/:name' => sub {
    my $filename = route_parameters->get('name');
    my $file_path = "/path/to/files/$filename";
    my $content_type = mime->for_file($filename);
    send_file($file_path, content_type => $content_type);
};

๐Ÿ“ฎ Adding Additional Response Headers

In Danceyland, enhancing the visitor experience often means adding new signposts without ripping down the existing ones. Similarly, in Dancer2, the push_response_header keyword lets you add new values to an existing response header without overwriting its current content. This is especially useful for headers like Set-Cookie, where you might need multiple values:

use Dancer2;

get '/set_cookies' => sub {
    push_response_header 'Set-Cookie' => 'user=alice';
    push_response_header 'Set-Cookie' => 'theme=light';
    return "Cookies have been set.";
};

In this example, two cookies get added to the response without overwriting each other. Both can live their truth. ๐Ÿช๐Ÿช


๐Ÿ”„ Automatic Serializing and Deserializing Data

In Danceyland, sometimes we need to hand data to our visitors in different formats โ€” like JSON, YAML, or XML. Dancer2 makes serializing data an absolute breeze. You can even configure Dancer2 to automatically serialize your responses based on the content type the client requested. Automatic? We love a hands-free moment. ๐Ÿ’…

Example: Serializing JSON for output

set serializer => 'JSON';

get '/ride-info' => sub {
    return { name => 'Roller Coaster', status => 'Open' };
};

With this setup, the response will automatically be serialized to JSON when it's requested. Chef's kiss. ๐Ÿ”ฅ

Example: Deserializing JSON from input

Dancer2 can also deserialize incoming requests automatically whenever a serializer is enabled. If the client sends JSON data, it gets parsed for you automatically:

post '/update-ride' => sub {
    my $data = request->data;  # Automatically deserialized JSON input
    return "Ride updated: " . $data->{name};
};

Here, the incoming request body is JSON, and Dancer2 deserializes it into a Perl data structure that you can reach through request->data. So convenient it's almost rude. โœจ


๐Ÿงฐ Manual Serializing and Deserializing

Dancer2 also provides manual serialization methods for those specific use cases where automatic serialization isn't enough, or just plain can't be used. Sometimes you gotta do it yourself, bestie. ๐Ÿซก

send_as

send_as lets you manually serialize your response into a specific format like JSON or YAML. This is useful when you need explicit control over the response format:

get '/info' => sub {
    return send_as JSON => { name => 'Danceyland', status => 'open' };
};

Here, send_as explicitly returns the response as JSON. You said what you said. ๐Ÿ’…

encode_json

In managing Danceyland, park data comes and goes in all sorts of formats. Dancer2 provides tools to handle these formats efficiently. The encode_json keyword lets you serialize a Perl data structure into a JSON string:

use Dancer2;

get '/data' => sub {
    my $data = { attraction => 'Roller Coaster', status => 'open' };
    return encode_json($data);
};

encode_json expects character data and returns UTF-8 encoded bytes.

Since you are manually serializing the response data, the serialization hooks provided by Dancer2 are not called. See "Hooks" for more information. ๐Ÿ‘€

to_json

to_json converts a Perl data structure to a JSON string. It's useful when you want to manually prepare JSON data before sending it:

my $json_string = to_json({ name => 'Danceyland', status => 'open' });

You likely want to use encode_json instead. Both return UTF-8 encoded bytes, but encode_json makes the intent explicit. Clarity is the vibe. โœจ

decode_json

You can decode incoming JSON payloads from the client with the decode_json keyword; it deserializes a JSON string into a Perl data structure:

use Dancer2;

post '/update' => sub {
    my $json_data = request->body;
    my $data = decode_json($json_data);
    # Now, do something with it...
};
Since you are manually deserializing the incoming JSON, the serialization hooks will not run. ๐Ÿ™ˆ

from_json

from_json also decodes JSON to a Perl data structure. The input is expected to be UTF-8 encoded bytes.

to_yaml

to_yaml takes a Perl datastructure and converts it into a YAML string. that's literally the whole job bestie ๐Ÿ’…

from_yaml

and from_yaml is the same thing but backwards โ€” it lets you deserialize a YAML string into a Perl data structure:

use Dancer2;

get '/config' => sub {
    my $yaml_data = request->body;
    my $data = from_yaml($yaml_data);
    # Now, do something with $data...
};

๐ŸŽŸ๏ธ Hooks ๐ŸŽŸ๏ธ

okay so hooks are like special backstage passes at Danceyland ๐Ÿ‘‘ they give you special access by letting you execute code at specific points in the request/response cycle. you can use them to modify requests, modify responses, or even add your own logic before or after routes are processed. it's giving main character energy fr fr.

Using Hooks with vars

sometimes you wanna share info across routes, right? you can use hooks to store data with vars, so it's there for later routes:

hook before => sub {
    my $request = request;
    vars->{visitor_id} = "VIP-" . $request->address;
};

get '/vip-area' => sub {
    return "Welcome, " . vars->{visitor_id} . "!";
};

so in this example we store the visitor's ID (based on their IP address) in the vars keyword during the before hook. that value gets used later in the /vip-area route to personalize the response. cute right? ๐ŸŽ€

Request Hooks

picture the ticket booth just inside the entrance of Danceyland โ€” the friendly gatekeeper scans every visitor's pass and gives them a sticker as they pass into the park:

hook before => sub {
    var sticker => 1;
};

the before hook runs before any other code, just before each request is handled. in this example we set a var, sticker, that can be used later in the request. obsessed.

and as each visitor leaves, a park employee stamps each person's hand so they can be readmitted later in the day:

hook after => sub {
    my $user = var user;
    $user->stamp_hand;
};

for this example, an object representing a user was stored in a var earlier in the request. on the user's way out (via the after hook), their hand gets stamped, and could be checked the next time the user enters the park to skip buying another ticket. genius ngl ๐Ÿฆ‹

Template Hooks

at Danceyland's Caricature Corner, the brilliant artist Picasso McGlitterpants creates a sketch of every guest who stops by. the caricature goes through several phases before it's ready to hang on your wall:

  • Before the sketch is drawn: Picasso sharpens their pencils
  • While drawing: the artist sneaks in little embellishments
  • When the sketch is done: Picasso proudly signs their masterpiece
  • Before handing it to you: the park staff pops it into a fancy frame

before the sketching starts, Picasso makes sure all the materials are in order:

hook before_template_render => sub {
    my $tokens = shift;

    # Picasso McGlitterpants sharpens their pencils and sets up the
    # paper before drawing begins.
    $tokens->{materials_ready} = 1;
};

template hooks let you manipulate data before or after a view (i.e., a template) is rendered. materials_ready gets added as a template token before the view is rendered. that token is made available in the template as [% materials_ready %]. ๐Ÿ’–

when the sketch is finished, Picasso adds their signature (with extra glitter, obviously โœจ) to make sure the caricature is authentic:

hook after_template_render => sub {
    my $content = shift;
    $$content .= "\n\n-- Signed by Picasso McGlitterpants ๐ŸŽจโœจ";
};

this happens after the view/template is rendered, but before it's shown to the user.

before the sketch is mounted, the artist sprinkles it with sparkles, making sure the presentation twinkles with Danceyland charm:

hook before_layout_render => sub {
    my ($tokens, $content) = @_;

    $tokens->{special_effects} = 'sparkles';
};

layout hooks apply after views are rendered, when the layout is going to be applied. the before_layout_render hook runs prior to the layout being applied. it's a useful place to check things like whether or not someone is authorized to view select content. ๐Ÿ‘€

at the very end, just before the guest receives their caricature, the staff pops it into a silvery frame so it's ready to hang at home:

hook after_layout_render => sub {
    my $content = shift;

    $$content = '<div class="silver-frame">' . $$content . "</div>";
};

the after_layout_render hook is the last chance to alter content before it gets sent to the browser. periodt.

Error Handling Hooks

even in Danceyland, sometimes the roller coaster gets stuck at the top, or the cotton candy machine makes a little too much fluff. that's when our friendly park attendants step in! ๐Ÿ˜ญ

error hooks let you step in whenever something goes wrong in an application. just like Danceyland staff rushing over with a smile and a toolkit, your error hook can log what happened, show a helpful message, or gently guide the guest to the right exit.

hook on_route_exception => sub {
    my ($error) = @_;
    warning "Picasso McGlitterpants spilled glitter on the server: $error";
};

or you can catch a general error and provide your own response:

hook before_error => sub {
    my ($error) = @_;
    $error->message("Oops! One of the rides jammed. " .
        "Please enjoy a free churro while we fix it.");
};

other error hooks include:

  • init_error

this runs right after a new Dancer2::Core::Error object is built. the new error object is passed to the hook as an argument.

  • after_error

this hook is called right after error is thrown, and receives a Dancer2::Core::Response object as an argument.

  • on_hook_exception

this hook is special, and is only called when an exception is caught in another hook, just before logging it and rethrowing it higher.

this hook receives as arguments:

  • A Dancer2::Core::App object
  • The error string
  • The name of the hook where the exception occurred

the hook name is the full hook name (e.g. core.app.route_exception).

if the function provided to on_hook_exception causes an exception itself, then this will be ignored (thus preventing a potentially recursive situation). however, it's still possible for the function to set a custom response and halt it (and optionally die), thus providing a method to render custom content in the event of an exception in a hook:

hook on_hook_exception => sub {
    my ($app, $error, $hook_name) = @_;
    $app->response->content("Oh noes! The popcorn machine " .
        "overheated and overflowed!");
    $app->response->halt;
};

think of error hooks as the ever-present Danceyland helpers: they make sure that when a mishap occurs, guests are cared for and can keep smiling on their way to the next attraction. ๐Ÿซถ

File Rendering Hooks

file rendering hooks get triggered when static files are served:

hook before_file_render => sub {
    my $file_path = shift;
    debug "Starting to serve file: $file_path";
};

hook after_file_render => sub {
    my $response = shift;
    debug "Finished serving file.";
};

Logging Hooks

logging hooks can be used to give extra information or context to Danceyland maintenance workers when something breaks down, or to ride operators to help ensure smooth operation of rides:

hook 'engine.logger.before' => sub {
    my ( $logger, $level, @messages ) = @_;
    push @messages, 'Request ID: ' . vars->{request_id};
};

hook 'engine.logger.after' => sub {
    my ( $logger, $level, @messages ) = @_;
    if( $level eq 'error' ) {
        # Add code to send an email here
    }
};

these hooks are useful when you wanna prepend or append information to a log message, write messages out in a specific format, or take additional action based on message severity. iykyk โœจ

Serializer Hooks

every great visit to Danceyland ends with a souvenir! serializer hooks are how Danceyland decides how to wrap up your experience before sending you home. maybe you get your caricature rolled up in a tube, maybe it's slipped into a glossy folder, or maybe Picasso McGlitterpants mails it to you as a postcard. ๐ŸŒธ

that's what serializers do: they take the "thing" (your response) and package it in the right format (JSON, YAML, plain text, etc.) so you can carry it with you outside the park.

for example:

set serializer => 'JSON';

now every souvenir is neatly wrapped in shiny JSON paper. ๐Ÿ’…

and if you want a custom wrapper, you can hook into the serializer process yourself:

hook before_serializer => sub {
    my ($content) = @_;
    debug "Preparing to wrap up: $content";
};

hook after_serializer => sub {
    my ($serialized) = @_;
    debug "Souvenir has been wrapped: $serialized";
};

so whenever you see serializer hooks, think of the Danceyland souvenir shop โ€” the place that makes sure you don't just have a memory, but also a neatly wrapped keepsake to take home. ๐ŸŽ


๐ŸŽข Handlers ๐ŸŽข

handlers are the rides that keep Danceyland running. Dancer2 provides several built-in handlers to manage files, pages, and routes. you can also create your own handlers to extend the park's offerings. no cap.

File Handler

the Dancer2::Handler::File component is used to serve static files, like images and stylesheets, from the public directory. it comes with two hooks; see the "Hooks" section for more info. ๐Ÿ–ผ๏ธ

Auto Page Handler

when a template matches a request, the Dancer2::Handler::AutoPage component is used. it automatically renders a template if one exists for the requested path.

so if someone visits /about and there's a template called about.tt, the auto page handler will automatically render that template. it just works โœจ

Writing Your Own Handlers

you can create your own handler by consuming the Dancer2::Core::Role::Handler role. this lets you define your own logic for processing routes:

package MyHandler;
use Moo;
with 'Dancer2::Core::Role::Handler';

sub methods { qw(GET) }

sub regexp { '/custom/:page' }

sub code {
    return sub {
        my $app = shift;
        my $page = $app->request->params->{page};
        return "You requested the custom page: $page";
    };
}
sub register {
    my ($self, $app) = @_;
    $app->add_route(
        method => $_,
        regexp => $self->regexp,
        code   => $self->code,
    ) for $self->methods;
}

โœจ Middleware โœจ

Okay so middleware in Dancer2 is literally the backstage crew of Danceyland ๐Ÿ’… โ€” the security guards, the cleaners, the people making sure the vibes stay immaculate. It does its thing in between the request and the response, so you can poke at or tweak the request/response without touching your app code at all. Iconic behaviour, honestly.

And adding it? So easy. You just use Plack, which is the engine chugging along underneath Dancer2 ๐Ÿซถ.

Why Use Middleware Instead of App Logic?

Now, bestie, you could technically hand-roll everything a middleware does right inside your own code. So why wouldn't you? Because middleware lets you mess with the request/response cycle without ever changing your app's core code. It's giving:

  • Managing sessions and cookies. ๐Ÿช
  • Handling authentication. ๐Ÿ”
  • Compressing responses or managing headers. ๐Ÿ“ฆ

Middleware lives right in-between the client's request and the app's response, so you get flexibility and you stop copy-pasting the same logic everywhere. Duplication? Not on our watch. ๐Ÿ’€

Bonus: middlewares are portable. You can use them across totally different code-bases, and if you're following best practices, they won't be glued to one specific web application. Main character energy fr fr.

Adding Middlewares

builder {
    enable 'Session';
    enable 'Static', path => qr{^/(images|css)/}, root => './public';
    app;
};

This example uses Plack middleware to run your sessions and serve static files. Two birds, one builder block, slay. โœจ


๐ŸŒˆ Asynchronous Programming ๐ŸŒˆ

In Danceyland we do not make our visitors wait in line ๐Ÿ˜ญ. Sometimes a task just takes forever โ€” like fetching data from some external service โ€” and async programming is how you run that stuff in the background so your app stays responsive and peppy.

Dancer2 supports asynchronous programming to help you handle I/O-bound operations way more efficiently. ๐Ÿ’…

Why Choose Async Over Blocking?

With async you can handle long-running tasks without blocking the rest of your app. Chef's kiss for:

  • Fetching data from an external API. ๐ŸŒ
  • Handling large uploads or downloads. ๐Ÿ“ฅ
  • Sending notifications or emails. ๐Ÿ“ง

Choosing the Right PSGI Server

Heads up bestie โ€” for async in Dancer2, your app has to be running on a PSGI server that supports event loops, like Twiggy. Those servers can actually handle async tasks, unlike traditional PSGI servers that use forking to deal with multiple parallel tasks. Not all servers are built the same, iykyk. ๐Ÿงš

Sending Content Asynchronously

Dancer2 hands you some keywords for writing asynchronous code. Here's the whole squad:

content

The content keyword sets the response content from within a delayed response. ๐ŸŽ€

delayed

Some Danceyland events โ€” like the grand parade ๐ŸŽ  โ€” are so worth watching but they take a while to finish. Same energy: Dancer2's delayed keyword kicks off an asynchronous response, so you can deliver long-running results or handle long-running operations without the whole park freezing up.

done

Once everything's set, done finalizes it and sends the response out to the visitor. โญ

flush

To send parts of the response incrementally, flush lets you stream content to the client inside a delayed response without closing the connection. Streaming queen behaviour. ๐ŸŒŠ

Example: Asynchronous HTTP Request

Here's how you fetch data asynchronously in Dancer2. Instead of sitting around waiting for a response, the request runs in the background and delivers the result whenever it's ready:

use Dancer2;
use Future::HTTP;

get '/fetch-ride-status' => sub {
    delayed {
        content 'The grand parade is starting!';
        flush;

        http_get('http://parade-status.com/api/v1/floats')->then( sub {
            my ($body, $headers) = @_;
            content $body;
            flush;
        });

        content 'The grand parade is finished!';
        done;
    };
};

In this example, we fetch the status of a ride asynchronously using http_get. The delayed keyword defers the response until the background request completes. The $resume callback is used to send the content back to the visitor once the request finishes. ๐Ÿฆ‹

For a deeper dive, check out these fantastic articles on async programming in Dancer2: https://advent.perldancer.org/2020/22 and https://advent.perldancer.org/2020/23. Obsessed with them, ngl. ๐Ÿ’–


๐ŸŒŸ AUTHOR ๐ŸŒŸ

Dancer Core Developers ๐Ÿ‘‘


This software is copyright (c) 2026 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. Free as in freedom, bestie. ๐Ÿซถ


And that's the whole manual, babes โ€” we made it to the end of Dancer2::Manual ๐ŸŽ‰ this very page generated for perl v5.42.3, dated 2026-09-20, so if you're reading this from the future, hi, hope the vibes held up. Go build something iconic. ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from