Dancer2::Tutorial(3)

πŸ’– NAME πŸ’–

Dancer2::Tutorial(3) β€” straight from the User Contributed Perl Documentation, bestie. This page is Dancer2::Tutorial - An example to get you dancing πŸ’ƒβœ¨

πŸŽ€ VERSION πŸŽ€

version 1.1.2 🫢

🌈 Tutorial Overview 🌈

Okay so this tutorial has three parts, and they build on one another, so you're meant to go through them in sequential order β€” no skipping, iykyk. πŸ’…

Part I is the longest part of this tutorial. It focuses on the basics of Dancer2 development by building a simple yet functional blog app called "dancr" that you can use to impress your friends, mates, and family. πŸ‘‘

In Part II, you'll learn about the preferred way to get your own web apps up and running by using the "dancer2" utility. We will take the script written in Part I and convert it into a proper Dancer2 app called "Dancr2", to help you gain an understanding of what the "dancer2" utility does for you. ✨

Finally, in Part III, we give you a taste of the power of plugins that other developers have written and will show you how to modify the "Dancr2" app to use a database plugin. πŸ”₯

This tutorial assumes you have some familiarity with Perl and that you know how to create and execute a Perl script on your computer. Some experience with web development is also greatly helpful but not entirely necessary. This tutorial is mostly geared toward developers, but website designers can get something out of it as well since the basics of templating are covered, plus it might be good for a designer to have a decent idea of how Dancer2 works. πŸ’–

πŸ‘‘ Part I: Let's Get Dancing! πŸ‘‘

Part I covers many of the basic concepts you'll need to know to lay a good foundation for your future development work with Dancer2 by building a simple micro-blogging app. πŸŽ€

πŸ¦‹ What is Dancer2? πŸ¦‹

Dancer2 is a micro-web framework, written in the Perl programming language, and is modeled after a Ruby web application framework called Sinatra (http://www.sinatrarb.com). πŸ’œ

When we say "micro" framework, we mean that Dancer2 aims to maximize your freedom and control by getting out of your way. "Micro" doesn't mean Dancer2 is only good for creating small apps. Instead, it means that Dancer2's primary focus is on taking care of a lot of the boring, technical details of your app for you and by creating an easy, clean routing layer on top of your app's code. It also means you have almost total control over the app's functionality and how you create and present your content. You will not be confined to someone else's approach to creating a website or app. 🌈

With Dancer2, you can build anything from a specialized content management system to providing a simple API for querying a database over the web. But you don't have to reinvent the wheel, either. Dancer2 has hundreds of plugins that you can take advantage of. You can add only the capabilities your app needs to keep complexity to a minimum. ⭐

As a framework, Dancer2 provides you with the tools and infrastructure you can leverage to deliver content on the web quickly, easily and securely. The tools Dancer2 provides, called "keywords," are commands that you use to build your app, access the data inside of it, and deliver it on the internet in many different formats. πŸ’…

Dancer2's keywords provide what is called a Domain Specific Language (DSL) designed specifically for the task of building apps. But don't let the technical jargon scare you off. Things will become clearer in our first code example which we will look at shortly. πŸ¦‹

🧚 Getting Dancer2 installed 🧚

First, we need to make sure you have Dancer2 installed. Typically, you will do that with one of the following two commands:

cpan Dancer2  # requires the cpan command to be installed and configured
cpanm Dancer2 # requires you have cpanminus installed

If you aren't familiar with installing Perl modules on your machine, you should read this guide (https://www.cpan.org/modules/INSTALL.html). You may also want to consult your OS's documentation or a knowledgeable expert. And, of course, your search engine of choice is always there for you, as well. 🫢

🌸 Your first Dancer2 "Hello World!" app 🌸

Now that you have Dancer2 installed, open up your favorite text editor and copy and paste the following lines of Perl code into it and save it to a file called "dancr.pl":

#!/usr/bin/env perl
use Dancer2;

get '/' => sub {
    return 'Hello World!';
};

start;

If you make this script executable and run it, it will fire up a simple, standalone web server that will display "Hello World!" when you point your browser to http://localhost:3000. Cool! 😎

Important note: We want to emphasize that writing a script file like this with a "start" command is not how you would typically begin writing a Dancer2 app. Part II of this tutorial will show you the recommended approach using the "dancer2" utility. For now, we want to stay focused on the fundamentals. πŸ’€

So, though our example app is very simple, there is a lot going on under the hood when we invoke "use Dancer2;" in our first line of code. We won't go into the gory details of how it all works. For now, it's enough for you to know that the Dancer2 module infuses your script with the ability to use Dancer2 keywords for building apps. Getting comfortable with the concept of keywords is probably the most important step you can take as a budding Dancer2 developer, and this tutorial will do its best to help foster your understanding of them. πŸ¦‹

The next line of code in our example (which spans three lines to make it more readable) is the route handler. Let's examine this line closely, because route handlers are at the core of how to build an app with Dancer2. πŸ‘‘

The syntax of a Dancer2 "route handler" has three parts:

  • an http method or http verb β€” in this example, we use the "get" keyword to tell Dancer2 that this route should apply to GET http requests. "get" is the first of many keywords that Dancer2 provides that we will cover in this tutorial. Those familiar with web development will know that a GET request is what we use to fetch information from a website. ✨
  • the route pattern β€” this is the bit of code that appears immediately after our "get" keyword. In this example it is a forward slash (/), wrapped in single quotes, and it represents the pattern we wish to match against the URL that the browser, or client, has requested. Web developers will immediately recognize that the forward slash symbolizes the root directory of our website. Experienced Perl programmers will pick up on the fact that the route pattern is nothing more than an argument for our "get" keyword. πŸ’…
  • the route action β€” this is the subroutine that returns our data. More precisely, it is a subroutine reference. The route action in our example returns a simple string, "Hello World!". Like the route pattern, the route action is nothing more than an argument to our "get" keyword. 🫢

Note that convention has us use the fat comma (=>) operator between the route pattern and the action to make our code more readable. But we could just as well have used a regular old comma to separate these arguments to our "get" method. Gotta love Perl for its flexibility. 😭

So to put our route pattern in the example into plain English, we are telling our app: "If the root directory is requested with the GET http method, send the string 'Hello World!' back in our response." Of course, since this is a web app, we also have to send back headers with our response. This is quietly taken care of for us by Dancer2 so we don't have to think about it. ⭐

The syntax for route handlers might seem a bit foreign for newer Perl developers. But rest assured there is nothing magical about it and it is all just plain old Perl under the hood. If you keep in mind that the keyword is a subroutine (or more precisely, a method) and that the pattern and action are arguments to the keyword, you'll pick it up in no time. Thinking of these keywords as "built-ins" to the Dancer2 framework might also eliminate any initial confusion about them. πŸ’œ

The most important takeaway here is that we build our app by adding route handlers which are nothing more than a collection of HTTP verbs, URL patterns, and actions. πŸŽ€

🐍 How about a little more involved example? 🐍

While investigating some Python web frameworks like Flask (http://flask.pocoo.org/) or Bottle (https://bottlepy.org/docs/dev/), I enjoyed the way they explained step-by-step how to build an example application which was a little more involved than a trivial example. This tutorial is modeled after them. ✨

Using the Flaskr (https://github.com/pallets/flask) sample application as my inspiration (OK, shamelessly plagiarised) I translated that application to the Dancer2 framework so I could better understand how Dancer2 worked. (I'm learning it too!) 😭

So "dancr" was born. 🌱

dancr is a simple "micro" blog which uses the SQLite (http://www.sqlite.org) database engine for simplicity's sake. You'll need to install sqlite on your server if you don't have it installed already. Consult your OS documentation for getting SQLite installed on your machine. πŸŽ€

πŸ“ Required Perl modules πŸ“

Obviously you need Dancer2 installed. You'll also need the Template Toolkit, File::Slurper, and DBD::SQLite modules. These all can be installed using your CPAN client with the following command:

cpan Template File::Slurper DBD::SQLite

πŸ’Ύ The database code πŸ’Ύ

We're not going to spend a lot of time on the database, as it's not really the point of this particular tutorial. Try not to dwell on this section too much if you don't understand all of it. πŸ™ˆ

Open your favorite text editor (http://www.vim.org) and create a schema definition called 'schema.sql' with the following content:

create table if not exists entries (
    id integer primary key autoincrement,
    title string not null,
    text string not null
);

Here we have a single table with three columns: id, title, and text. The 'id' field is the primary key and will automatically get an ID assigned by the database engine when a row is inserted. πŸ‘‘

We want our application to initialize the database automatically for us when we start it. So, let's edit the 'dancr.pl' file we created earlier and give it the ability to talk to our database with the following subroutines: (Or, if you prefer, you can copy and paste the finished dancr.pl script, found near the end of Part I in this tutorial, into the file all at once and then just follow along with the tutorial.) πŸ’…

sub connect_db {
    my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
        or die $DBI::errstr;

    return $dbh;
}
   sub init_db {
       my $db     = connect_db();
       my $schema = read_text('./schema.sql');
       $db->do($schema)
           or die $db->errstr;
   }

Nothing too extra in there, bestie, I promise 🫢 β€” it's just regular DBI, except for that little setting('database') moment. More on that in a sec. For now just trust me: that expression gives you the location of the database file.

And heads up: in Part III of the tutorial we'll show you how to use the Dancer2::Plugin::Database module for an easier way to configure and manage database connections for your Dancer2 apps. Foreshadowing ✨


πŸŽ€ Our first route handler πŸŽ€

Okay bestie, back to the actual business of learning Dancer2 β€” time to build our app's first route handler for the root URL. Take the route handler in our simple example above and swap it out for this one:

   get '/' => sub {
       my $db  = connect_db();
       my $sql = 'select id, title, text from entries order by id desc';

       my $sth = $db->prepare($sql)
           or die $db->errstr;

       $sth->execute
           or die $sth->errstr;

       template 'show_entries.tt', {
           msg           => get_flash(),
           add_entry_url => uri_for('/add'),
           entries       => $sth->fetchall_hashref('id'),
       };
   };

So our new route handler is literally the same as the one in our first example, except the route action does so much more work now. Main character energy πŸ’…

One thing you might not have clocked right away: the semicolon at the end of the route handler. Ngl this trips up newer Perl coders, and it's also a bug source for experienced ones who just forget to add it. We need that semicolon because we're creating a reference to a subroutine β€” and because the Perl compiler demands it and we must obey if we want our code to actually run. Them's the rules πŸŽ€

Alright, let's stare at this route's action a little closer. The first few lines are standard DBI. The important Dancer2 bit is the template keyword at the end of the action β€” that's what tells Dancer2 to push the output through one of its templating engines. There are so many template engines you can use with Dancer2. In this tutorial we're using Template Toolkit, which gives you a whole lot more flexibility than the simple default Dancer2 template engine. Iconic behavior ✨

Templates all go into a views/ directory, which sits in the same directory as our dancr.pl script. Optionally you can create a layout template that provides a consistent look and feel for all your views. We'll build our own layout template, cleverly named main.tt, a little later in this tutorial. Spoiler alert πŸ“

So what's the deal with the hashref as the second argument to the template directive? Those are all the parameters we want to pass into our template. We've got a msg field that displays a message to the user when something happens β€” like a new entry is posted, or the user logs in or out. It's called a "flash" message because we only want it showing up one time, not every single time the / URL is rendered. One-time moment, love that for us πŸ™ˆ

The uri_for directive tells Dancer2 to give us a URI for that specific route β€” in this case, the route to post a new entry into the database. Now you might be like "why not just hardcode the /add URI in our application or templates?" and the best reason not to is that it removes a whole layer of flexibility about where you "mount" the web application. Like, sure, the application is coded to use the root URL /, but it might be better in the future to locate it under its own URL route (maybe /dancr?) β€” and at that point we'd have to go through our application and the templates and update the URLs and just hope we didn't miss any of them 😭. By using the uri_for Dancer2 method, we can load the application wherever we like and not have to modify the application at all. Slay.

Finally, the entries field holds a hashref with the results from our database query. Those results get rendered in the template itself, so we just pass them in. Done and dusted πŸ’–

So what does the show_entries.tt template look like? This:

 [% IF session.logged_in %]
   <form action="[% add_entry_url %]" method=post class=add-entry>
     <dl>
       <dt>Title:
       <dd><input type=text size=30 name=title>
       <dt>Text:
       <dd><textarea name=text rows=5 cols=40></textarea>
       <dd><input type=submit value=Share>
     </dl>
   </form>
 [% END %]
 <ul class=entries>
 [% IF entries.size %]
   [% FOREACH id IN entries.keys.nsort %]
     <li><h2>[% entries.$id.title | html %]</h2>[% entries.$id.text | html %]
   [% END %]
 [% ELSE %]
   <li><em>Unbelievable. No entries here so far</em>
 [% END %]
 </ul>

Go ahead and make a views/ directory in the same directory as the script and drop this file in it. 🧚

Again, this isn't a tutorial about Template Toolkit, so we're gonna gloss over the syntax here and just point out that section starting with <ul class=entries>. That's the section where the database query results get displayed. You can also peep at the very top some discussion about a session β€” more on that soon πŸ‘‘

The only other Template Toolkit thing that has to be mentioned here is the | html in [% entries.$id.title | html %]. That's a filter β€” http://www.template-toolkit.org/docs/manual/Filters.html#section_html β€” that converts characters like < and > into &lt; and &gt;. That way the browser displays them as content on the page instead of just... including them. If we didn't do this, the browser might interpret the content as part of the page, and a malicious user could smuggle in all kinds of bad code that would then run in another user's browser 😭. This is called Cross Site Scripting β€” https://en.wikipedia.org/wiki/Cross-site_scripting β€” or XSS, and you should make sure to avoid it by always filtering data that came in from the web when you display it in a template. No cap, this one matters πŸ’€


🌈 Other HTTP verbs 🌈

There are 8 defined HTTP verbs defined in RFC 2616 β€” http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9 β€” and those are: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT. Of those, the majority of web apps focus on the verbs that closely map to the CRUD (Create, Retrieve, Update, Delete) operations most database-driven applications need to implement. It's giving very organized ✨

Also, the PATCH verb was defined in RFC5789 β€” http://tools.ietf.org/html/rfc5789 β€” and it's intended as a "partial PUT", sending just the changes required to the entity in question. How that gets handled is entirely down to your app; it'll vary depending on the type of entity and the serialization in use.

Dancer2's keywords currently supports GET, PUT/PATCH, POST, DELETE, OPTIONS, which map to Retrieve, Update, Create, Delete respectively. Let's take a look now at the /add route handler which handles a POST operation:

   post '/add' => sub {
       if ( not session('logged_in') ) {
           send_error("Not logged in", 401);
       }

       my $db = connect_db();
       my $sql = 'insert into entries (title, text) values (?, ?)';
       my $sth = $db->prepare($sql)
           or die $db->errstr;

       $sth->execute(
           body_parameters->get('title'),
           body_parameters->get('text')
       ) or die $sth->errstr;

       set_flash('New entry posted!');
       redirect '/';
   };

Same as before: the HTTP verb kicks off the handler, then the route, then a subroutine to do the thing β€” in this case it inserts a new entry into the database. πŸ”₯

The first check in the subroutine makes sure the user sending the data is logged in. If not, the application returns an error and stops processing. Otherwise, it's standard DBI stuff. Let me insert (heh, heh) a blatant plug here for always, always using parameterized INSERTs in your application SQL statements. It's the only way to be sure your application won't be vulnerable to SQL injection. (See http://www.bobby-tables.com for correct INSERT examples in multiple languages β€” go look, fr fr.) Here we're using the body_parameters convenience method to pull in the parameters in the current HTTP request. (You can see the 'title' and 'text' form parameters in the show_entries.tt template above.) Those values get inserted into the database, then we set a flash message for the user and redirect her back to the root URL. Chef's kiss πŸ‘‘

Worth mentioning: the "flash message" is not part of Dancer2 β€” it's part of this specific application. We have to implement it ourselves. DIY era πŸ’…


πŸ¦‹ Logins and sessions πŸ¦‹

Dancer2 comes with a simple in-memory session manager right out of the box. It also supports a bunch of other session engines including YAML, memcached, browser cookies and others. We'll just stick with the in-memory model, which works great for development and tutorials, but won't persist across server restarts and won't scale very well in "real world" production scenarios. Just so you know 🫢

Configuration options

To use sessions in our application, we have to tell Dancer2 to activate the session handler and initialize a session manager. To do that, we add some configuration directives toward the top of our 'dancr.pl' file. But there are more options than just the session engine we want to set:

   set 'database'        => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
   set 'session'         => 'Simple';
   set 'template'        => 'template_toolkit';
   set 'logger'          => 'console';
   set 'log'             => 'debug';
   set 'show_stacktrace' => 1;
   set 'startup_info'    => 1;

Hopefully these are fairly self-explanatory. We want the Simple session engine, the Template Toolkit template engine, logging enabled (at the 'debug' level with output to the console instead of a file), we want to show errors to the web browser, and print a banner at the server start with information such as versions and the environment. ✨

Dancer2 doesn't impose any limits on what parameters you can set using the set syntax. For this application we're going to embed our single username and password into the application itself:

   set 'username' => 'admin';
   set 'password' => 'password';

Hopefully no one will ever guess our clever password! 😭 Obviously you'll want a more sophisticated user authentication scheme in any sort of non-tutorial application, but this is good enough for our purposes.

In Part II of our tutorial, we will show you how to use Dancer2's configuration files to manage these options and set up different environments for your app using different configuration files. For now, we're gonna keep it simple and leave that discussion for later. Patience, bestie πŸ’œ

Logging in

Now that dancr is configured to handle sessions, let's take a look at the URL handler for the /login route.

   any ['get', 'post'] => '/login' => sub {
       my $err;
               if ( request->method() eq "POST" ) {
                   # process form input
                   if ( body_parameters->get('username') ne setting('username') ) {
                       $err = "Invalid username";
                   }
                   elsif ( body_parameters->get('password') ne setting('password') ) {
                       $err = "Invalid password";
                   }
                   else {
                       session 'logged_in' => true;
                       set_flash('You are logged in.');
                       return redirect '/';
                   }
               }

               # display login form
               template 'login.tt', {
                   err => $err,
               };
           };

Okay so this is the first handler that’s out here accepting two different verb types β€” a GET for a human just browsing to the URL, and a POST for the browser submitting the user’s input to the web app. Since we’re handling two different verbs, we check what verb is in the request. If it’s not a POST, we drop down to the template directive and display the login.tt template:

         <h2>Login</h2>
         [% IF err %]<p class=error><strong>Error:</strong> [% err %][% END %]
         <form action="[% login_url %]" method=post>
           <dl>
             <dt>Username:
             <dd><input type=text name=username>
             <dt>Password:
             <dd><input type=password name=password>
             <dd><input type=submit value=Login>
           </dl>
         </form>

This is even simpler than our show_entries.tt template β€” but wait β€” there’s a login_url template parameter and we’re only passing in the err parameter. Where’s the missing parameter? πŸ‘€ It’s being generated and sent to the template in a before_template_render directive, and we’ll come back to that in a moment or two.

So the user fills out the login.tt template and submits it back to the /login route handler. We now check the user input against our application settings, and if the input is incorrect, we alert the user. Otherwise the application starts a session and sets the logged_in session parameter to the true() value. Dancer2 exports both a true() and false() convenience method, which we use here. After that, it’s another flash message and back to the root URL handler. πŸ’…

πŸšͺ Logging out πŸ’¨

And finally, we need a way to clear our user’s session with the customary logout procedure.

    get '/logout' => sub {
        app->destroy_session;
        set_flash('You are logged out.');
        redirect '/';
    };

app->destroy_session; is Dancer2’s way to remove a stored session. We notify the user she is logged out and route her back to the root URL once again.

You might wonder how we can then set a value in the session in set_flash, because we just destroyed the session.

Destroying the session has removed the data from the persistence layer (which is the memory of our running application, because we are using the "simple" session engine). If we write to the session now, it will actually create a completely new session for our user. This new, empty session will have a new session ID, which Dancer2 tells the user’s browser about in the response. When the browser requests the root URL, it will send this new session ID to our application.


🎨 Layout and static files πŸ–ΌοΈ

We still have a missing puzzle piece or two. First, how can we use Dancer2 to serve our CSS stylesheet? Second, where are flash messages displayed? Third, what about the before_template_render directive?

πŸ—‚οΈ Serving static files

In Dancer2, static files should go into the public/ directory, but in the application itself be sure to omit the public/ element from the path. For example, the stylesheet for dancr lives in dancr/public/css/style.css but is served from http://localhost:3000/css/style.css.

If you wanted to build a mostly static web site you could simply write route handlers like this one:

    get '/' => sub {
        send_file 'index.html';
    };

where index.html would live in your public/ directory.

send_file does exactly what it says: it loads a static file, then sends the contents of that file to the user. 🫢

Let’s go ahead and create our style sheet. In the same directory as your dancr.pl script, issue the following commands:

    mkdir public && mkdir public/css && touch public/css/style.css

Next add the following css to the public/css/style.css file you just created:

    body            { font-family: sans-serif; background: #eee; }
    a, h1, h2       { color: #377ba8; }
    h1, h2          { font-family: 'Georgia', serif; margin: 0; }
    h1              { border-bottom: 2px solid #eee; }
    h2              { font-size: 1.2em; }

    .page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                      padding: 0.8em; background: white; }
    .entries        { list-style: none; margin: 0; padding: 0; }
    .entries li     { margin: 0.8em 1.2em; }
    .entries li h2  { margin-left: -1em; }
    .add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
    .add-entry dl   { font-weight: bold; }
    .metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                      margin-bottom: 1em; background: #fafafa; }
    .flash          { background: #cee5F5; padding: 0.5em;
                      border: 1px solid #aacbe2; }
    .error          { background: #f0d6d6; padding: 0.5em; }

Be sure to save the file. πŸ’Ύ

🧩 Layouts

I mentioned earlier in the tutorial that it is possible to create a "layout" template. In dancr, that layout is called main and it’s set up by putting in a directive like this:

    set layout => 'main';

near the top of your web application. This tells Dancer2’s template engine that it should look for a file called main.tt in views/layouts/ and insert the calls from the template directive into a template parameter called content.

Here is the simple layout file we will use for this web application. Go ahead and add this to the main.tt file in the views/layouts/ directory.

  <!doctype html>
  <html>
  <head>
    <title>dancr</title>
    <link rel=stylesheet type=text/css href="[% css_url %]">
  </head>
  <body>
    <div class=page>
    <h1>dancr</h1>
       <div class=metanav>
       [% IF not session.logged_in %]
         <a href="[% login_url %]">log in</a>
       [% ELSE %]
         <a href="[% logout_url %]">log out</a>
       [% END %]
    </div>
    [% IF msg %]
      <div class=flash> [% msg %] </div>
    [% END %]
    [% content %]
  </div>
  </body>
  </html>

Aha! You now see where the flash message "msg" parameter gets rendered. You can also see where the content from the specific route handlers is inserted (the fourth line from the bottom in the "content" template parameter).

But what about all those other *_url template parameters? πŸ€”

πŸͺ„ Using before_template_render

Dancer2 has a way to manipulate the template parameters before they’re passed to the engine for processing. It’s before_template_render. Using this keyword, you can generate and set the URIs for the /login and /logout route handlers and the URI for the stylesheet. This is handy for situations like this where there are values which are re-used consistently across all (or most) templates. This cuts down on code-duplication and makes your app easier to maintain over time since you only need to update the values in this one place instead of everywhere you render a template.

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

        $tokens->{'css_url'}    = request->base . 'css/style.css';
        $tokens->{'login_url'}  = uri_for('/login');
        $tokens->{'logout_url'} = uri_for('/logout');
    };

Here again I’m using uri_for instead of hardcoding the routes. This code block is executed before any of the templates are processed so that the template parameters have the appropriate values before being rendered.


🧡 Putting it all together ✨

Here’s the complete 'dancr.pl' script from start to finish.

    use Dancer2;
    use DBI;
    use File::Spec;
    use File::Slurper qw/ read_text /;
    use Template;

    set 'database'        => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
    set 'session'         => 'Simple';
    set 'template'        => 'template_toolkit';
    set 'logger'          => 'console';
    set 'log'             => 'debug';
    set 'show_stacktrace' => 1;
    set 'startup_info'    => 1;
    set 'username'        => 'admin';
    set 'password'        => 'password';
    set 'layout'          => 'main';

    sub set_flash {
        my $message = shift;

        session flash => $message;
    }

    sub get_flash {
        my $msg = session('flash');
        session->delete('flash');

        return $msg;
    }

    sub connect_db {
        my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
            or die $DBI::errstr;

        return $dbh;
    }

    sub init_db {
        my $db     = connect_db();
        my $schema = read_text('./schema.sql');
        $db->do($schema)
            or die $db->errstr;
    }

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

        $tokens->{'css_url'}    = request->base . 'css/style.css';
        $tokens->{'login_url'}  = uri_for('/login');
        $tokens->{'logout_url'} = uri_for('/logout');
    };

    get '/' => sub {
        my $db  = connect_db();
        my $sql = 'select id, title, text from entries order by id desc';

        my $sth = $db->prepare($sql)
            or die $db->errstr;

        $sth->execute
            or die $sth->errstr;

        template 'show_entries.tt', {
            msg           => get_flash(),
            add_entry_url => uri_for('/add'),
            entries       => $sth->fetchall_hashref('id'),
        };
    };

    post '/add' => sub {
        if ( not session('logged_in') ) {
            send_error("Not logged in", 401);
        }

        my $db  = connect_db();
        my $sql = 'insert into entries (title, text) values (?, ?)';

        my $sth = $db->prepare($sql)
            or die $db->errstr;

        $sth->execute(
            body_parameters->get('title'),
            body_parameters->get('text')
        ) or die $sth->errstr;

        set_flash('New entry posted!');
        redirect '/';
    };

    any ['get', 'post'] => '/login' => sub {
        my $err;
if ( request->method() eq "POST" ) {
    # process form input
    if ( body_parameters->get('username') ne setting('username') ) {
        $err = "Invalid username";
    }
    elsif ( body_parameters->get('password') ne setting('password') ) {
        $err = "Invalid password";
    }
    else {
        session 'logged_in' => true;
        set_flash('You are logged in.');
        return redirect '/';
    }
}

# display login form
template 'login.tt', {
    err => $err,
};

};

get '/logout' => sub {
    app->destroy_session;
    set_flash('You are logged out.');
    redirect '/';
};

init_db();
start;

Advanced route moves ✨

okay so bestie, route matching is SO much more than what we just showed you. like, there's a whole world here πŸ¦‹

You can match routes with regular expressions, or you can match pieces of a route like /hello/:name β€” and that :name slice literally magically turns into a named parameter in your handler so you can do stuff with it. it's giving "free variable, no cap" πŸŽ€

You can explore this and other advanced concepts by reading the Dancer2::Manual. go be nosy, i support it πŸ’…


🌈 Part II: Taking Advantage of the dancer2 Utility to Set Up New Apps 🌈

So in Part I we took a whole entire ordinary Perl script and turned it into a cute little web app to teach you basic Dancer2 concepts. And like β€” starting with a simple script did make the concepts easier to teach, that part ate. But it did not show you how a real, actual Dancer2 developer builds an app. πŸ™ˆ

So let's show you how things really get done. buckle up πŸ’–

Creating a new app πŸŽ€

So now that you've got a better idea of what goes into building an app with Dancer2, it's time to cha-cha with the dancer2 utility β€” which will save you a LOT of time and effort by setting up directories, files, and default configuration settings for you. it's the ultimate "i gotchu" friend 🫢

The dancer2 utility got installed on your machine when you installed the Dancer2 distribution. Hop over to the command line, cd into a directory you have permission to write to, and issue this:

dancer2 gen -a Dancr2

That command should output something like the following to the console β€” and yes it's a lot, slay:

+ Dancr2
+ Dancr2/config.yml
+ Dancr2/Makefile.PL
+ Dancr2/MANIFEST.SKIP
+ Dancr2/.dancer
+ Dancr2/cpanfile
+ Dancr2/bin
+ Dancr2/bin/app.psgi
+ Dancr2/environments
+ Dancr2/environments/development.yml
+ Dancr2/environments/production.yml
+ Dancr2/lib
+ Dancr2/lib/Dancr2.pm
+ Dancr2/public
+ Dancr2/public/favicon.ico
+ Dancr2/public/500.html
+ Dancr2/public/dispatch.cgi
+ Dancr2/public/404.html
+ Dancr2/public/dispatch.fcgi
+ Dancr2/public/css
+ Dancr2/public/css/error.css
+ Dancr2/public/css/style.css
+ Dancr2/public/images
+ Dancr2/public/images/perldancer.jpg
+ Dancr2/public/images/perldancer-bg.jpg
+ Dancr2/public/javascripts
+ Dancr2/public/javascripts/jquery.js
+ Dancr2/t
+ Dancr2/t/001_base.t
+ Dancr2/t/002_index_route.t
+ Dancr2/views
+ Dancr2/views/index.tt
+ Dancr2/views/layouts
+ Dancr2/views/layouts/main.tt

So what you just did?? created a fully functional app in Dancer2 with one single command. one. 🀯

The new app, named Dancr2, won't do anything particularly useful until you add your own routes to it β€” that part's on you bestie β€” but it does take care of many of the tedious tasks of setting up an app for you. chef's kiss πŸ§‘β€πŸ³

The files and folders that were generated, and that you see listed above, provide a convenient scaffolding, or skeleton, upon which you can build your app. The default skelelton provides you with basic error pages, css, javascript, graphics, tests, templates and other files which you are free to modify and customize to your liking. it's a whole vibe, decorate it however πŸ’…

And if you don't like the default skeleton provided to you by Dancer, the dancer2 command allows you to generate your own custom skeletons πŸ‘‘ Consult "BOOTSTRAPPING-A-NEW-APP" in Dancer2::Manual for further details on this and other capabilities of the dancer2 utility.

Getting the new app up and running with Plack πŸ¦‹

In Part I, we used the start command in our script to launch a server to serve our app. Things are a little different when using dancer2, however. πŸ‘€

You'll notice that the dancer2 utility created a bin/ directory with a file in it called app.psgi. This is the file we use to get our app up and running.

Let's see how to do that β€” first cd into the Dancr2 directory, then start the server using the plackup command:

cd Dancr2;
plackup -p 5000 bin/app.psgi

If all went well, you'll be able to see the Dancr2 home page by visiting:

http://localhost:5000

The web page you see there gives you some very basic advice for tuning and modifying your app, and tells you where you can go for more info to learn about developing apps with Dancer2 (like this handy tutorial!). hi, that's me, i'm the tutorial πŸŽ€

Our Dancr2 app is served on a simple web server provided by Plack. Plack is PSGI compliant software β€” hence the psgi extension for our file in the bin/ directory. Plack and PSGI is beyond the scope of this tutorial, but you can learn more by visiting the Plack website at http://plackperl.org/. go read it, i'll wait 🫢

For now, all you need to know is that if you're deploying an app for use by just yourself or a handful of people on a local network, Plack alone may do the trick. More typically you'd use Plack in conjunction with other server software to make your app much more robust. But in the early stages of your app's development, a simple Plack server is more than likely all you need. baby steps, they're iconic πŸ’–

To learn more about the different ways for deploying your app, see the Dancer2 Deployment Manual.

Porting dancr.pl over to the new Dancr2 app 🧚

Ok so now that we've got our new Dancr2 app up and running (slay), it's time to learn how to take advantage of what the dancer2 utility set up for us β€” by porting our dancr.pl script from Part I into Dancr2. let's gooo πŸ”₯

The lib/ directory

The lib/ directory in our Dancr2 app is where our app.psgi file will expect our code to live. So let's take a peek at the file that got generated for us in there:

cat lib/Dancr2.pm

You'll see something like the following bit of code, which provides a single route to our app's home page and loads the index template:

package Dancr2;
use Dancer2;

our $VERSION = '0.1';

get '/' => sub {
    template 'index' => { title => 'Dancr2' };
};

true;

The first thing you'll notice is that instead of a script, we're using a module, Dancr2, to package our code. Modules make it easer to pull off many powerful tricks β€” like packaging our app across several discrete modules. We'll let the manual explain that more advanced technique. i'm not gonna gatekeep but also i'm not gonna write a whole essay, yk? 😌

Updating the Dancr2 module

Now that we know where to put our code, let's update the Dancr2.pm module with our original dancr.pl code. πŸ”¨

Remove the existing sample route in Dancr2.pm and replace it with the code from our dancr.pl file. You'll have to make a couple of adjustments to the dancr.pl code β€” like removing the use Dancer2; line since it's already provided by our module. You'll also want to be sure to remove the start; line as well from the end of the file. little cleanup, no drama 🧹

When you're done, Dancr2.pm should look something close to this:

package Dancr2;
use Dancer2;

our $VERSION = '0.1';

# Our original dancr.pl code with some minor tweaks
use DBI;
use File::Spec;
use File::Slurper qw/ read_text /;
use Template;

set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
set 'session'  => 'YAML';
...

<snip> # The rest of the stuff </snip>

...

sub init_db {
    my $schema = read_text('./schema.sql');
    $db->do($schema)
        or die $db->errstr;
}

get '/logout' => sub {
    app->destroy_session;
    set_flash('You are logged out.');
    redirect '/';
};

init_db();

Finally β€” and this one matters, bestie β€” to avoid getting an error in the init_db subroutine when it tries to load our schema file, copy over the schema.db file to the root directory of the Dancr2 app:

cp /path/to/dancr.pl/schema.db /path/to/Dancr2;

Ok now that we've got the code moved over, let's move the assets from dancr.pl to our new app πŸ’…

The public/ directory

As mentioned in Part I, our static assets go into our public/ directory. If you followed along with the tutorial in Part I, you should have a public/ directory with a public/css subdirectory and a file called style.css within that.

Dancer2 has conveniently generated the public/css directory for us, which has a default css file. Let's copy the style sheet from our original app so our new app can use it:

# Note: This command overwrites the default style sheet. Move it or copy
# it if you wish to preserve it.

cp /path/to/dancr.pl/public/css/style.css /path/to/Dancr2/public/css;
⚠️ heads up bestie: that command overwrites the default style sheet. Move it or copy it if you wish to preserve it. don't say i didn't warn you πŸ’€

The views directory

Along with our public/ directory, Dancer has also provided a views/ directory, which β€” as we covered β€” serves as the home for our templates. Let's get those copied over now:

# NOTE: This command will overwrite the default main.tt tempalte file. Move
# it or copy it if you wish to preserve it.

cp -r /path/to/dancr.pl/views/* /path/to/Dancr2/views;
πŸŽ€ again: this overwrites the default main.tt template file. Move it or copy it if you wish to preserve it!

Does it work?

If you followed the instructions here closely, your Dancr2 app should be working. ✨

Shut down any running Plack servers, then issue the same plackup command to see if it runs:

cd /path/to/Dancr2
plackup -p 5000 bin/app.psgi

If you see any errors, get them resolved until the app loads. no skipping, you got this πŸ’ͺ

Configuring Your App 🌸

In Part I, you configured your app with a series of set statements near the top of your file. Now we're gonna show you a better way to configure your app β€” using Dancer2's configuration files. glow up incoming πŸ‘‘

Okay so your skeleton hands your app three different config files, bestie. The first two live in the environments/ folder of your app: development.yml and production.yml. As you can probably guess, development.yml has settings meant for when you're developing the app, and production.yml has settings that make more sense when other people are actually using it. The third config file is in the root directory of your app and is named config.yml. That one holds the settings common to all environments, but the environment config files can override them. And yes β€” you can still override any config file settings in your modules using the set command. πŸ’…

Let's peek at development.yml first. Open that file in your text editor and take a look inside. It has a bunch of helpful comments and the following five settings sprinkled throughout:

logger: "console"
log: "core"
show_stacktrace: 1
startup_info: 1

The first four settings duplicate many of the settings in our new Dancr2 app. So in the spirit of DRY (don't repeat yourself), edit your Dancr2 module and delete the four lines that correspond to these four settings. ✨

Then, in the configuration file, be sure to change the value for the log setting from core to debug so it matches the value we had in our module.

We'll leave it up to you what you want to do with the fourth setting, startup_info. You can read about that setting, along with all the other settings, in the configuration manual. πŸ‘‘

Finally, let's add a new setting to the configuration file for session with the following line:

session: "Simple"

Then delete the corresponding setting from your Dancr2 module.

Alright, our Dancr2 app is a little leaner and meaner now. πŸ¦‹ Next, open the main config.yml file and look for the settings in there that are also duplicated in our app's module. There are two:

layout: "main"
template: "simple"

Leave layout as is, but change the template setting to template_toolkit. Then edit your Dancr2 module file and delete these two settings. πŸŽ€

Finally, add the following configuration settings to the .yml file:

username: "admin"
password: "password"

Then you delete these two settings from the Dancr2 module as well.

So, if you've been following along, you now have only the following set command in your Dancr2 module, related to the database configuration:

set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');

We will get rid of this setting in Part III of the tutorial. All the rest of the settings have been transferred to our configuration files. Nice! πŸ’–

We still have a little more cleanup we can do. Now that Dancer2 knows we're using Template::Toolkit, we can delete the use Template; line from our module. 🧚

Now start the app with the plackup command and check to see that everything works. By default, Dancer2 will load the development environment configuration. When it comes time to put your app into production, you can load the production.yml file configuration with plackup's --env switch like so:

plackup -p 5000 --env production bin/app.psgi

πŸ’ƒ Keep on Dancing! πŸ’ƒ

This concludes Part II of our tutorial where we showed you how to take advantage of the dancer2 utility to set up an app skeleton to make it really easy to get started developing your own apps. Periodt. πŸ”₯

Part III will refine our app a little further by showing you how to use plugins so you can start capitalizing on all the great work contributed by other Dancer2 developers. 🌈🫢

πŸ’… Part III: Plugins, Your Many Dancing Partners 🌈

Dancer2 is fully riding the open-source software revolution, bestie β€” it makes using plugins so easy that you can mix them into your app and give it shiny new functionality. In Part III we’re updating our new Dancr2 app to use Dancer2::Plugin::Database, which gives you enough skills to go out and explore other plugins on your own. 🫢

πŸ“¦ Installing plugins

Like Dancer2 itself, Dancer2 plugins live on CPAN. Use your favorite method for downloading and installing the Dancer2::Plugin::Database module on your machine. We recommend using cpanminus like so:

cpanm Dancer2::Plugin::Database

πŸ”Œ Using plugins

Using a plugin literally couldn’t be easier. Just add this line to your Dancr2 module below the use Dancer2; line in your module:

use Dancer2::Plugin::Database;

βš™οΈ Configuring plugins

Plugins can be configured with the YAML configuration files mentioned in Part II of this tutorial. Let’s edit the development.yml file and add our database configuration there. Below the last line in that file, add the following lines, being careful to keep the indentation as you see it here:

  plugins:                 # all plugin configuration settings go in this section
    Database:              # the name of our plugin
      driver: "SQLite"     # driver we want to use
      database: "dancr.db" # where the database will go in our app
                           # run a query when connecting to the datbase:
      on_connect_do: [ "create table if not exists entries (id integer primary key autoincrement, title string not null, text string not null)" ]

Here, we direct our database plugin to use the SQLite driver and to place the database in the root directory of our Dancr2. The on_connect_db setting tells the plugin to run an SQL query when it connects with the database to create a table for us if it doesn’t already exist. ✨

πŸ› οΈ Modifying our database code in the Dancr2 module

Now it’s time to modify our Dancr2 module so it will use the plugin to query the database instead of our own code. There are a few things to do. First, we will delete the code we no longer need. 🧹

Since our configuration file tells the plugin where our database is, we can delete this line:

set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');

And since the database plugin will create our database connection and initialize our database for us, we can scrap the following two subroutines and line from our module:

sub connect_db {
    my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
        or die $DBI::errstr;

    return $dbh;
}

sub init_db {
    my $db = connect_db();
    my $schema = read_text('./schema.sql');
    $db->do($schema)
        or die $db->errstr;
}

init_db(); # Found at the bottom of our file

With that done, let’s now take advantage of a hook the plugin provides us that we can use to handle certain events by adding the following command to our module to handle database errors:

hook 'database_error' => sub {
    my $error = shift;
    die $error;
};

Now let’s make a few adjustments to the bits of code that make the database queries. In our get '/' route, change all instances of $db with database and remove all the die calls since we now have a hook to handle the errors for us. When you are done, your route should look something like this:

get '/' => sub {
    my $sql = 'select id, title, text from entries order by id desc';
    my $sth = database->prepare($sql);
    $sth->execute;
    template 'show_entries.tt', {
        msg           => get_flash(),
        add_entry_url => uri_for('/add'),
        entries       => $sth->fetchall_hashref('id'),
    };
};

Make the same changes to the post '/add' route to transform it into this:

post '/add' => sub {
    if ( not session('logged_in') ) {
        send_error("Not logged in", 401);
    }

    my $sql = 'insert into entries (title, text) values (?, ?)';
    my $sth = database->prepare($sql);
    $sth->execute(
        body_parameters->get('title'),
        body_parameters->get('text')
    );

    set_flash('New entry posted!');
    redirect '/';
};

Our last step is to get rid of the following lines which we no longer need, thanks to our plugin:

use DBI;
use File::Spec;
use File::Slurper qw/ read_text /;

That’s it! Now start your app with plackup to make sure you don’t get any errors and then point your browser to test the app to make sure it works as expected. If it doesn’t, double and triple check your configuration settings and your module’s code which should now look like this:

package Dancr2;
use Dancer2;
use Dancer2::Plugin::Database;

our $VERSION = '0.1';

my $flash;

sub set_flash {
    my $message = shift;

    $flash = $message;
}

sub get_flash {
    my $msg = $flash;
    $flash  = "";

    return $msg;
}

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

    $tokens->{'css_url'}    = request->base . 'css/style.css';
    $tokens->{'login_url'}  = uri_for('/login');
    $tokens->{'logout_url'} = uri_for('/logout');
};

hook 'database_error' => sub {
    my $error = shift;
    die $error;
};

get '/' => sub {
    my $sql = 'select id, title, text from entries order by id desc';
    my $sth = database->prepare($sql);
    $sth->execute;
    template 'show_entries.tt', {
        msg           => get_flash(),
        add_entry_url => uri_for('/add'),
        entries       => $sth->fetchall_hashref('id'),
    };
};

post '/add' => sub {
    if ( not session('logged_in') ) {
        send_error("Not logged in", 401);
    }

    my $sql = 'insert into entries (title, text) values (?, ?)';
    my $sth = database->prepare($sql);
    $sth->execute(
        body_parameters->get('title'),
        body_parameters->get('text')
    );

    set_flash('New entry posted!');
    redirect '/';
};

any ['get', 'post'] => '/login' => sub {
    my $err;

    if ( request->method() eq "POST" ) {
        # process form input
        if ( params->{'username'} ne setting('username') ) {
            $err = "Invalid username";
        }
        elsif ( params->{'password'} ne setting('password') ) {
            $err = "Invalid password";
        }
        else {
            session 'logged_in' => true;
            set_flash('You are logged in.');
            return redirect '/';
        }
    }

    # display login form
    template 'login.tt', {
        err => $err,
    };

};

get '/logout' => sub {
    app->destroy_session;
    set_flash('You are logged out.');
    redirect '/';
};

true;

πŸš€ Next steps

Congrats! You are now using the database plugin like a boss. The database plugin does a lot more than what we showed you here. We’ll leave it up to you to consult the Dancer2::Plugin::Database to unlock its full potential. πŸ”₯

There are many more plugins for you to explore. You now know enough to install and experiment with them. Some of the more popular and useful plugins are listed at Dancer2::Plugins. You can also search CPAN with Dancer2::Plugin for a more comprehensive listing.

If you are feeling really inspired, you can learn how to extend Dancer2 with your own plugins by reading Dancer2::Plugin. πŸ’–

πŸ’ƒ Happy dancing! πŸ’ƒ

I hope these tutorials have been helpful and interesting enough to get you exploring Dancer2 on your own. The framework is still under development but it’s definitely mature enough to use in a production project.

Happy dancing! πŸŽ€

πŸ§ͺ One more thing: Test! πŸ§ͺ

Before we go, we want to mention that Dancer2 makes it very easy to run automated tests on your app to help you find bugs. If you are new to testing, we encourage you to start learning how. Your future self will thank you. The effort you put into creating tests for your app will save you many hours of frustration in the long run. Unfortunately, until we get Part IV of this tutorial written, you’ll have to consult the Dancer2 testing documentation for more details on how to test your app. 😭

Enjoy! ✨

πŸ‘€ SEE ALSO

The CSS stylesheet is copied verbatim from the Flaskr example application and is subject to their license:

Copyright (c) 2010, 2013 by Armin Ronacher and contributors.

Some rights reserved.

Redistribution and use in source and binary forms of the software as well as documentation, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission.

✍️ AUTHOR

Dancer Core Developers

This software is copyright (c) 2024 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.

πŸ’– That’s a wrap on Dancer2::Tutorial(3) β€” this page comes from perl v5.42.0, dated 2024-12-12. Byeee, go dance! πŸŽ€

πŸ“¦ where this page comes from