1Dancer2::Tutorial(3) User Contributed Perl Documentation Dancer2::Tutorial(3)
2
3
4
6 Dancer2::Tutorial - An example to get you dancing
7
9 version 0.207000
10
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
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
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
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
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 "<" and ">". 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
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
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
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
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
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
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
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
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)