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.207000
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
38       <http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/> sample
39       application as my inspiration (OK, shamelessly plagiarised) I
40       translated that application to the Dancer2 framework so I could better
41       understand how Dancer2 worked. (I'm learning it too!)
42
43       So "Dancr" was born.
44
45       Dancr is a simple "micro" blog which uses the SQLite
46       <http://www.sqlite.org> database engine for simplicity's sake.  (You'll
47       need to install sqlite if you don't have it installed already.)
48

Required perl modules

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

The database

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

Our first route handler

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

Other HTTP verbs

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

Sessions and logins

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

Layout and static files

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

Putting it all together

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

Advanced route moves

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

Next Steps

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

SEE ALSO

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

AUTHOR

681       Dancer Core Developers
682
684       This software is copyright (c) 2018 by Alexis Sukrieh.
685
686       This is free software; you can redistribute it and/or modify it under
687       the same terms as the Perl 5 programming language system itself.
688
689
690
691perl v5.28.1                      2018-11-14              Dancer2::Tutorial(3)
Impressum