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.300000
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 <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
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
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
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 "<" and ">". 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
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
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
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
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
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
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
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
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)