1Dancer2::Tutorial(3)  User Contributed Perl Documentation Dancer2::Tutorial(3)
2
3
4

NAME

6       Dancer2::Tutorial - An example to get you dancing
7

VERSION

9       version 0.300000
10

What is Dancer2?

12       Dancer2 is a "micro" web framework which is modeled after a Ruby
13       framework called Sinatra <http://www.sinatrarb.com> that constructs web
14       applications by building a list of HTTP verbs, URLs (called routes) and
15       methods to handle that type of traffic to that specific URL.
16
17         use Dancer2;
18
19         get '/' => sub {
20           return 'Hello World!';
21         };
22
23         start;
24
25       This example shows a single HTTP verb "GET" followed by the root URL
26       "/" and an anonymous subroutine which returns the string "Hello World!"
27       If you were to run this example, it would display "Hello World!" when
28       you point your browser at <http://localhost:3000>.
29

How about a little more involved example?

31       That's the reason I wrote this tutorial.  While I was investigating
32       some Python web frameworks like Flask <http://flask.pocoo.org/> or
33       Bottle <https://bottlepy.org/docs/dev/> I enjoyed the way they
34       explained step by step how to build an example application which was a
35       little more involved than a trivial example.
36
37       Using the Flaskr <https://github.com/pallets/flask> sample application
38       as my inspiration (OK, shamelessly plagiarised) I translated that
39       application to the Dancer2 framework so I could better understand how
40       Dancer2 worked. (I'm learning it too!)
41
42       So "Dancr" was born.
43
44       Dancr is a simple "micro" blog which uses the SQLite
45       <http://www.sqlite.org> database engine for simplicity's sake.  (You'll
46       need to install sqlite if you don't have it installed already.)
47

Required perl modules

49       Obviously you need Dancer2.  You also need the Template Toolkit,
50       File::Slurper, and DBD::SQLite.  These all can be installed using your
51       CPAN client, as in:
52
53         cpan Dancer2 Template File::Slurper DBD::SQLite
54

The database

56       We're not going to spend a lot of time on the database, as it's not
57       really the point of this particular tutorial.  Open your favorite text
58       editor <http://www.vim.org> and create a schema definition called
59       'schema.sql' with the following content:
60
61         create table if not exists entries (
62           id integer primary key autoincrement,
63           title string not null,
64           text string not null
65         );
66
67       Here we have a single table with three columns: id, title, and text.
68       The 'id' field is the primary key and will automatically get an ID
69       assigned by the database engine when a row is inserted.
70
71       We want our application to initialize the database automatically for us
72       when we start it, so next, create a file called 'dancr.pl'. (The entire
73       file is listed below, so don't worry about copying each of these
74       fragments into 'dancr.pl' as you read through this document.) We're
75       going to put the following subroutines in that file:
76
77         sub connect_db {
78           my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database')) or
79              die $DBI::errstr;
80
81           return $dbh;
82         }
83
84         sub init_db {
85           my $db = connect_db();
86           my $schema = read_text('./schema.sql');
87           $db->do($schema) or die $db->errstr;
88         }
89
90       Nothing too fancy in here, I hope. Standard DBI except for the
91       "setting('database')" thing - more on that in a bit.  For now, just
92       assume that the expression evaluates to the location of the database
93       file.
94
95       (Note that you may want to look at the Dancer2::Plugin::Database module
96       for an easy way to configure and manage database connections for your
97       Dancer2 apps, but the above will suffice for this tutorial.)
98

Our first route handler

100       Let's tackle our first route handler now, the one for the root URL '/'.
101       This is what it looks like:
102
103         get '/' => sub {
104           my $db = connect_db();
105           my $sql = 'select id, title, text from entries order by id desc';
106           my $sth = $db->prepare($sql) or die $db->errstr;
107           $sth->execute or die $sth->errstr;
108           template 'show_entries.tt', {
109              'msg' => get_flash(),
110              'add_entry_url' => uri_for('/add'),
111              'entries' => $sth->fetchall_hashref('id'),
112           };
113         };
114
115       As you can see, the handler is created by specifying the HTTP verb
116       'get', the '/' URL to match, and finally, a subroutine to do something
117       once those conditions have been satisfied.  Something you might not
118       notice right away is the semicolon at the end of the route handler.
119       Since the subroutine is actually a coderef, it requires a semicolon.
120
121       Let's take a closer look at the subroutine.  The first few lines are
122       standard DBI. The only new concept as part of Dancer2 is that
123       "template" directive at the end of the handler.  That tells Dancer2 to
124       process the output through one of its templating engines.  In this
125       case, we're using Template Toolkit which offers a lot more flexibility
126       than the simple default Dancer2 template engine.
127
128       Templates all go into the "views/" directory. Optionally, you can
129       create a "layout" template which provides a consistent look and feel
130       for all of your views.  We'll construct our own layout template
131       cleverly named main.tt a little later in this tutorial.
132
133       What's going on with the hashref as the second argument to the template
134       directive?  Those are all of the parameters we want to pass into our
135       template.  We have a "msg" field which displays a message to the user
136       when an event happens like a new entry is posted, or the user logs in
137       or out.  It's called a "flash" message because we only want to display
138       it one time, not every time the / URL is rendered.
139
140       The "uri_for" directive tells Dancer2 to provide a URI for that
141       specific route, in this case, it is the route to post a new entry into
142       the database.  You might ask why we don't simply hardcode the "/add"
143       URI in our application or templates.  The best reason not to do that is
144       because it removes a layer of flexibility as to where to "mount" the
145       web application.  Although the application is coded to use the root URL
146       "/" it might be better in the future to locate it under its own URL
147       route (maybe "/dancr"?)  - at that point we'd have to go through our
148       application and the templates and update the URLs and hope we didn't
149       miss any of them.  By using the "uri_for" Dancer2 method, we can easily
150       load the application wherever we like and not have to modify the
151       application at all.
152
153       Finally, the "entries" field contains a hashref with the results from
154       our database query.  Those results will be rendered in the template
155       itself, so we just pass them in.
156
157       So what does the show_entries.tt template look like? This:
158
159         [% IF session.logged_in %]
160           <form action="[% add_entry_url %]" method=post class=add-entry>
161             <dl>
162               <dt>Title:
163               <dd><input type=text size=30 name=title>
164               <dt>Text:
165               <dd><textarea name=text rows=5 cols=40></textarea>
166               <dd><input type=submit value=Share>
167             </dl>
168           </form>
169         [% END %]
170         <ul class=entries>
171         [% IF entries.size %]
172           [% FOREACH id IN entries.keys.nsort %]
173             <li><h2>[% entries.$id.title | html %]</h2>[% entries.$id.text | html %]
174           [% END %]
175         [% ELSE %]
176           <li><em>Unbelievable. No entries here so far</em>
177         [% END %]
178         </ul>
179
180       Again, since this isn't a tutorial specifically about Template Toolkit,
181       I'm going to gloss over the syntax here and just point out the section
182       which starts with "<ul class=entries>" - this is the section where the
183       database query results are displayed.  You can also see at the very top
184       some discussion about a session - more on that soon.
185
186       The only other Template Toolkit related thing that has to be mentioned
187       here is the "| html" in "[% entries.$id.title | html %]". That's a
188       filter <http://www.template-
189       toolkit.org/docs/manual/Filters.html#section_html> to convert
190       characters like "<" and ">" to "&lt;" and "&gt;". This way they will be
191       displayed by the browser as content on the page rather than just
192       included. If we did not do this, the browser might interpret content as
193       part of the page, and a malicious user could smuggle in all kinds of
194       bad code that would then run in another user's browser. This is called
195       Cross Site Scripting <https://en.wikipedia.org/wiki/Cross-
196       site_scripting> or XSS and you should make sure to avoid it by always
197       filtering data that came in from the web when you display it in a
198       template.
199

Other HTTP verbs

201       There are 8 defined HTTP verbs defined in RFC 2616
202       <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9>: OPTIONS,
203       GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT.  Of these, the majority
204       of web applications focus on the verbs which closely map to the CRUD
205       (Create, Retrieve, Update, Delete) operations most database-driven
206       applications need to implement.
207
208       In addition, the "PATCH" verb was defined in RFC5789
209       <http://tools.ietf.org/html/rfc5789>, and is intended as a "partial
210       PUT" - sending just the changes required to the entity in question.
211       How this would be handled is down to your app, it will vary depending
212       on the type of entity in question and the serialization in use.
213
214       Dancer2 currently supports GET, PUT/PATCH, POST, DELETE, OPTIONS which
215       map to Retrieve, Update, Create, Delete respectively.  Let's take a
216       look now at the "/add" route handler which handles a POST operation.
217
218         post '/add' => sub {
219            if ( not session('logged_in') ) {
220               send_error("Not logged in", 401);
221            }
222
223            my $db = connect_db();
224            my $sql = 'insert into entries (title, text) values (?, ?)';
225            my $sth = $db->prepare($sql) or die $db->errstr;
226            $sth->execute(
227                body_parameters->get('title'),
228                body_parameters->get('text')
229            ) or die $sth->errstr;
230
231            set_flash('New entry posted!');
232            redirect '/';
233         };
234
235       As before, the HTTP verb begins the handler, followed by the route, and
236       a subroutine to do something - in this case, it will insert a new entry
237       into the database.
238
239       The first check in the subroutine is to make sure the user sending the
240       data is logged in. If not, the application returns an error and stops
241       processing.  Otherwise, we have standard DBI stuff. Let me insert (heh,
242       heh) a blatant plug here for always, always using parameterized INSERTs
243       in your application SQL statements.  It's the only way to be sure your
244       application won't be vulnerable to SQL injection. (See
245       <http://www.bobby-tables.com> for correct INSERT examples in multiple
246       languages.) Here we're using the "body_parameters" convenience method
247       to pull in the parameters in the current HTTP request. (You can see the
248       'title' and 'text' form parameters in the show_entries.tt template
249       above.) Those values are inserted into the database, then we set a
250       flash message for the user and redirect her back to the root URL.
251
252       It's worth mentioning that the "flash message" is not part of Dancer2,
253       but a part of this specific application. We need to implement it
254       ourself.
255
256         sub set_flash {
257             my $message = shift;
258
259             session flash => $message;
260         }
261
262         sub get_flash {
263             my $msg = session('flash');
264             session->delete('flash');
265
266             return $msg;
267         }
268
269       We need a way to save our temporary message, and a way to get it back
270       out.  Since it is a temporary message that should only be shown on the
271       page immediately following the one where the value was set, we need to
272       delete it once it was read.
273

Sessions and logins

275       A good way to implement this "flash message" mechanic is to write the
276       message to the user's session. The session stores data for a specific
277       user for the whole time she uses the application. It persists over all
278       requests this user makes. You can read more about how to use the
279       session in our manual.
280
281       Dancer2 comes with a simple in-memory session manager out of the box.
282       It supports a bunch of other session engines including YAML, memcached,
283       browser cookies and others.  For this application we're going to stick
284       with the in-memory model which works great for development and
285       tutorials, but won't persist across server restarts or scale very well
286       in "real world" production scenarios.
287
288   Configuration options
289       To use sessions in our application, we have to tell Dancer2 to activate
290       the session handler and initialize a session manager.  To do that, we
291       add some configuration directives toward the top of our 'dancr.pl'
292       file.  But there are more options than just the session engine we want
293       to set.
294
295         set 'database'     => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
296         set 'session'      => 'Simple';
297         set 'template'     => 'template_toolkit';
298         set 'logger'       => 'console';
299         set 'log'          => 'debug';
300         set 'show_errors'  => 1;
301         set 'startup_info' => 1;
302         set 'warnings'     => 1;
303
304       Hopefully these are fairly self-explanatory. We want the Simple session
305       engine, the Template Toolkit template engine, logging enabled (at the
306       'debug' level with output to the console instead of a file), we want to
307       show errors to the web browser, log access attempts and log Dancer2
308       warnings (instead of silently ignoring them).
309
310       In a more sophisticated application you would want to put these
311       configuration options into a configuration file, but for this tutorial,
312       we're going to keep it simple.  Dancer2 also supports the notion of
313       application environments, meaning you can create a configuration file
314       for your development instance, and another config file for the
315       production environment (with things like debugging and showing errors
316       disabled perhaps). Dancer2 also doesn't impose any limits on what
317       parameters you can set using the "set" syntax.  For this application
318       we're going to embed our single username and password into the
319       application itself:
320
321         set 'username' => 'admin';
322         set 'password' => 'password';
323
324       Hopefully no one will ever guess our clever password!  Obviously, you
325       will want a more sophisticated user authentication scheme in any sort
326       of non-tutorial application but this is good enough for our purposes.
327
328   Logging in
329       Now that Dancr is configured to handle sessions, let's take a look at
330       the URL handler for the "/login" route.
331
332         any ['get', 'post'] => '/login' => sub {
333            my $err;
334
335            if ( request->method() eq "POST" ) {
336              # process form input
337              if ( body_parameters->get('username') ne setting('username') ) {
338                $err = "Invalid username";
339              }
340              elsif ( body_parameters->get('password') ne setting('password') ) {
341                $err = "Invalid password";
342              }
343              else {
344                session 'logged_in' => true;
345                set_flash('You are logged in.');
346                return redirect '/';
347              }
348           }
349
350           # display login form
351           template 'login.tt', {
352             'err' => $err,
353           };
354         };
355
356       This is the first handler which accepts two different verb types, a GET
357       for a human browsing to the URL and a POST for the browser to submit
358       the user's input to the web application.  Since we're handling two
359       different verbs, we check to see what verb is in the request.  If it's
360       not a POST, we drop down to the "template" directive and display the
361       login.tt template:
362
363         <h2>Login</h2>
364         [% IF err %]<p class=error><strong>Error:</strong> [% err %][% END %]
365         <form action="[% login_url %]" method=post>
366           <dl>
367             <dt>Username:
368             <dd><input type=text name=username>
369             <dt>Password:
370             <dd><input type=password name=password>
371             <dd><input type=submit value=Login>
372           </dl>
373         </form>
374
375       This is even simpler than our show_entries.tt template - but wait -
376       there's a "login_url" template parameter and we're only passing in the
377       "err" parameter. Where's the missing parameter?  It's being generated
378       and sent to the template in a "before_template_render" directive -
379       we'll come back to that in a moment or two.
380
381       So the user fills out the login.tt template and submits it back to the
382       "/login" route handler.  We now check the user input against our
383       application settings and if the input is incorrect, we alert the user,
384       otherwise the application starts a session and sets the "logged_in"
385       session parameter to the "true()" value. Dancer2 exports both a
386       "true()" and "false()" convenience method which we use here.  After
387       that, it's another flash message and back to the root URL handler.
388
389   Logging out
390       And finally, we need a way to clear our user's session with the
391       customary logout procedure.
392
393         get '/logout' => sub {
394            app->destroy_session;
395            set_flash('You are logged out.');
396            redirect '/';
397         };
398
399       "app->destroy_session;" is Dancer2's way to remove a stored session.
400       We notify the user she is logged out and route her back to the root URL
401       once again.
402
403       You might wonder how we can then set a value in the session in
404       "set_flash", because we just destroyed the session.
405
406       Destroying the session has removed the data from the persistence layer
407       (which is the memory of our running application, because we are using
408       the "simple" session engine). If we write to the session now, it will
409       actually create a completely new session for our user. This new, empty
410       session will have a new session ID, which Dancer2 tells the user's
411       browser about in the response.  When the browser requests the root URL,
412       it will send this new session ID to our application.
413

Layout and static files

415       We still have a missing puzzle piece or two.  First, how can we use
416       Dancer2 to serve our CSS stylesheet? Second, where are flash messages
417       displayed?  Third, what about the "before_template_render" directive?
418
419   Serving static files
420       In Dancer2, static files should go into the "public/" directory, but in
421       the application itself be sure to omit the "public/" element from the
422       path.  For example, the stylesheet for Dancr lives in
423       "dancr/public/css/style.css" but is served from
424       <http://localhost:3000/css/style.css>.
425
426       If you wanted to build a mostly static web site you could simply write
427       route handlers like this one:
428
429         get '/' => sub {
430            send_file 'index.html';
431         };
432
433       where index.html would live in your "public/" directory.
434
435       "send_file" does exactly what it says: it loads a static file, then
436       sends the contents of that file to the user.
437
438   Layouts
439       I mentioned near the beginning of this tutorial that it is possible to
440       create a "layout" template. In Dancr, that layout is called "main" and
441       it's set up by putting in a directive like this:
442
443         set layout => 'main';
444
445       near the top of your web application.  This tells Dancer2's template
446       engine that it should look for a file called main.tt in
447       "dancr/views/layouts/" and insert the calls from the "template"
448       directive into a template parameter called "content".
449
450       For this web application, the layout template looks like this:
451
452         <!doctype html>
453         <html>
454         <head>
455           <title>Dancr</title>
456           <link rel=stylesheet type=text/css href="[% css_url %]">
457         </head>
458         <body>
459           <div class=page>
460           <h1>Dancr</h1>
461              <div class=metanav>
462              [% IF not session.logged_in %]
463                <a href="[% login_url %]">log in</a>
464              [% ELSE %]
465                <a href="[% logout_url %]">log out</a>
466              [% END %]
467           </div>
468           [% IF msg %]
469             <div class=flash> [% msg %] </div>
470           [% END %]
471           [% content %]
472         </div>
473         </body>
474         </html>
475
476       Aha! You now see where the flash message "msg" parameter gets rendered.
477       You can also see where the content from the specific route handlers is
478       inserted (the fourth line from the bottom in the "content" template
479       parameter).
480
481       But what about all those other *_url template parameters?
482
483   Using "before_template_render"
484       Dancer2 has a way to manipulate the template parameters before they're
485       passed to the engine for processing. It's "before_template_render".
486       Using this directive, you can generate and set the URIs for the
487       "/login" and "/logout" route handlers and the URI for the stylesheet.
488       This is handy for situations like this where there are values which are
489       re-used consistently across all (or most) templates.  This cuts down on
490       code-duplication and makes your app easier to maintain over time since
491       you only need to update the values in this one place instead of
492       everywhere you render a template.
493
494         hook before_template_render => sub {
495            my $tokens = shift;
496
497            $tokens->{'css_url'} = request->base . 'css/style.css';
498            $tokens->{'login_url'} = uri_for('/login');
499            $tokens->{'logout_url'} = uri_for('/logout');
500         };
501
502       Here again I'm using "uri_for" instead of hardcoding the routes.  This
503       code block is executed before any of the templates are processed so
504       that the template parameters have the appropriate values before being
505       rendered.
506

Putting it all together

508       Here's the complete 'dancr.pl' script from start to finish.
509
510        use Dancer2;
511        use DBI;
512        use File::Spec;
513        use File::Slurper qw/ read_text /;
514        use Template;
515
516        set 'database'     => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
517        set 'session'      => 'Simple';
518        set 'template'     => 'template_toolkit';
519        set 'logger'       => 'console';
520        set 'log'          => 'debug';
521        set 'show_errors'  => 1;
522        set 'startup_info' => 1;
523        set 'warnings'     => 1;
524        set 'username'     => 'admin';
525        set 'password'     => 'password';
526        set 'layout'       => 'main';
527
528        sub set_flash {
529            my $message = shift;
530
531            session flash => $message;
532        }
533
534        sub get_flash {
535            my $msg = session('flash');
536            session->delete('flash');
537
538            return $msg;
539        }
540
541        sub connect_db {
542            my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database')) or
543                die $DBI::errstr;
544
545            return $dbh;
546        }
547
548        sub init_db {
549            my $db = connect_db();
550            my $schema = read_text('./schema.sql');
551            $db->do($schema) or die $db->errstr;
552        }
553
554        hook before_template_render => sub {
555            my $tokens = shift;
556
557            $tokens->{'css_url'} = request->base . 'css/style.css';
558            $tokens->{'login_url'} = uri_for('/login');
559            $tokens->{'logout_url'} = uri_for('/logout');
560        };
561
562        get '/' => sub {
563            my $db = connect_db();
564            my $sql = 'select id, title, text from entries order by id desc';
565            my $sth = $db->prepare($sql) or die $db->errstr;
566            $sth->execute or die $sth->errstr;
567            template 'show_entries.tt', {
568                'msg' => get_flash(),
569                'add_entry_url' => uri_for('/add'),
570                'entries' => $sth->fetchall_hashref('id'),
571            };
572        };
573
574        post '/add' => sub {
575            if ( not session('logged_in') ) {
576                send_error("Not logged in", 401);
577            }
578
579            my $db = connect_db();
580            my $sql = 'insert into entries (title, text) values (?, ?)';
581            my $sth = $db->prepare($sql) or die $db->errstr;
582            $sth->execute(
583                body_parameters->get('title'),
584                body_parameters->get('text')
585            ) or die $sth->errstr;
586
587            set_flash('New entry posted!');
588            redirect '/';
589        };
590
591        any ['get', 'post'] => '/login' => sub {
592            my $err;
593
594            if ( request->method() eq "POST" ) {
595                # process form input
596                if ( body_parameters->get('username') ne setting('username') ) {
597                    $err = "Invalid username";
598                }
599                elsif ( body_parameters->get('password') ne setting('password') ) {
600                    $err = "Invalid password";
601                }
602                else {
603                    session 'logged_in' => true;
604                    set_flash('You are logged in.');
605                    return redirect '/';
606                }
607           }
608
609           # display login form
610           template 'login.tt', {
611               'err' => $err,
612           };
613
614        };
615
616        get '/logout' => sub {
617           app->destroy_session;
618           set_flash('You are logged out.');
619           redirect '/';
620        };
621
622        init_db();
623        start;
624

Advanced route moves

626       There's a lot more to route matching than shown here. For example, you
627       can match routes with regular expressions, or you can match pieces of a
628       route like "/hello/:name" where the ":name" piece magically turns into
629       a named parameter in your handler for manipulation.
630

Next Steps

632       Hopefully this effort has been helpful and interesting enough to get
633       you exploring Dancer2 on your own. Dancer2 is suitable for projects of
634       all shapes and sizes, and you can easily build a solution tailored to
635       your particular needs.
636
637       There is certainly a lot left to cover. We suggest you take a stroll
638       through the manualfor more detailed information about the topics
639       covered in this tutorial, and for a more thorough explanation of all
640       things Dancer2-related. The cookbook will show you how to handle some
641       handy tips and tricks when working with Dancer2. Additionally, there
642       are a lot of great plugins
643       <https://metacpan.org/search?q=Dancer2%3A%3APlugin> which extend and
644       enhance the capabilities of the framework.
645
646       Happy dancing!
647

SEE ALSO

649       ·   <http://perldancer.org>
650
651       ·   <http://github.com/PerlDancer/Dancer2>
652
653       ·   Dancer2::Plugins
654
656       The CSS stylesheet is copied verbatim from the Flaskr example
657       application and is subject to their license:
658
659       Copyright (c) 2010, 2013 by Armin Ronacher and contributors.
660
661       Some rights reserved.
662
663       Redistribution and use in source and binary forms of the software as
664       well as documentation, with or without modification, are permitted
665       provided that the following conditions are met:
666
667       ·   Redistributions of source code must retain the above copyright
668           notice, this list of conditions and the following disclaimer.
669
670       ·   Redistributions in binary form must reproduce the above copyright
671           notice, this list of conditions and the following disclaimer in the
672           documentation and/or other materials provided with the
673           distribution.
674
675       ·   The names of the contributors may not be used to endorse or promote
676           products derived from this software without specific prior written
677           permission.
678

AUTHOR

680       Dancer Core Developers
681
683       This software is copyright (c) 2019 by Alexis Sukrieh.
684
685       This is free software; you can redistribute it and/or modify it under
686       the same terms as the Perl 5 programming language system itself.
687
688
689
690perl v5.30.1                      2020-01-29              Dancer2::Tutorial(3)
Impressum