1Dancer::Tutorial(3) User Contributed Perl Documentation Dancer::Tutorial(3)
2
3
4
6 Dancer::Tutorial - An example to get you dancing
7
9 version 1.3513
10
12 Dancer 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 Dancer;
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 <http://bottle.paws.de/docs/dev/index.html> I enjoyed the way
34 they explained step by step how to build an example application which
35 was a little more involved that a trivial example.
36
37 Using the Flaskr
38 <https://github.com/mitsuhiko/flask/tree/master/examples/flaskr/>
39 sample application as my inspiration (OK, shamelessly plagiarised) I
40 translated that application to the Dancer framework so I could better
41 understand how Dancer 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.
47
49 Obviously you need Dancer. You also need the Template Toolkit,
50 File::Slurp, and DBD::SQLite. These all can be installed using your
51 CPAN client, as in:
52
53 cpan Dancer Template File::Slurp 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.
58
59 Create the database by running the follow from the shell:
60
61 $ cat - | sqlite3 database
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 ^D
68
69 The above creates a single table with three columns: id, title, and
70 text. The 'id' field is the primary key and will automatically get an
71 ID assigned by the database engine when a row is inserted.
72
73 We want our application to initialize the database automatically for us
74 when we start it, so open your favorite text editor
75 <http://www.vim.org> and create a file called 'dancr.pl'. We're going
76 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_file('./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 file location for the database
94 file.
95
96 (Note that you may want to look at the Dancer::Plugin::Database module
97 for an easy way to configure and manage database connections for your
98 Dancer 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' and the URL to match, '/' and finally a subroutine to do
118 something once those conditions have been satisfied. Something you
119 might not notice right away is the semicolon at the end of the route
120 handler. Since the subroutine actually is a coderef, it requires a
121 semicolon.
122
123 Let's take a closer look at the subroutine. The first few lines are
124 standard DBI. The only new concept as part of Dancer is that "template"
125 directive at the end of the handler. That tells Dancer to process the
126 output through one of its templating engines. In this case, we're
127 using Template Toolkit which offers a lot more flexibility than the
128 simple default Dancer template engine.
129
130 Templates all go into the "views/" directory. Optionally, you can
131 create a "layout" template which provides a consistent look and feel
132 for all of your views. We'll construct our own layout template
133 cleverly named main.tt a little later in this tutorial.
134
135 What's going on with the hashref as the second argument to the template
136 directive? Those are all of the parameters we want to pass into our
137 template. We have a "msg" field which displays a message to the user
138 when an event happens like a new entry is posted, or the user logs in
139 or out. It's called a "flash" message because we only want to display
140 it one time, not every time the / URL is rendered.
141
142 The "uri_for" directive tells Dancer to provide a URI for that specific
143 route, in this case, it is the route to post a new entry into the
144 database. You might ask why we don't simply hardcode the "/add" URI in
145 our application or templates. The best reason not to do that is
146 because it removes a layer of flexibility on where to "mount" the web
147 application. Although the application is coded to use the root URL "/"
148 it might be better in the future to locate it under its own URL route
149 (maybe "/dancr"?) - at that point we'd have to go through our
150 application and the templates and update the URLs and hope we didn't
151 miss any of them. By using the "uri_for" Dancer method, we can easily
152 load the application wherever we like and not have to modify the
153 application at all.
154
155 Finally, the "entries" field contains a hashref with the results from
156 our database query. Those results will be rendered in the template
157 itself, so we just pass them in.
158
159 So what does the show_entries.tt template look like? This:
160
161 <% IF session.logged_in %>
162 <form action="<% add_entry_url %>" method=post class=add-entry>
163 <dl>
164 <dt>Title:
165 <dd><input type=text size=30 name=title>
166 <dt>Text:
167 <dd><textarea name=text rows=5 cols=40></textarea>
168 <dd><input type=submit value=Share>
169 </dl>
170 </form>
171 <% END %>
172 <ul class=entries>
173 <% IF entries.size %>
174 <% FOREACH id IN entries.keys.nsort %>
175 <li><h2><% entries.$id.title %></h2><% entries.$id.text %>
176 <% END %>
177 <% ELSE %>
178 <li><em>Unbelievable. No entries here so far</em>
179 <% END %>
180 </ul>
181
182 Again, since this isn't a tutorial specifically about Template Toolkit,
183 I'm going to gloss over the syntax here and just point out the section
184 which starts with "<ul class=entries>" - this is the section where the
185 database query results are displayed. You can also see at the very top
186 some discussion about a session - more on that soon.
187
189 There are 8 defined HTTP verbs defined in RFC 2616
190 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9>: OPTIONS,
191 GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT. Of these, the majority
192 of web applications focus on the verbs which closely map to the CRUD
193 (Create, Retrieve, Update, Delete) operations most database driven
194 applications need to implement.
195
196 In addition, the "PATCH" verb was defined in RFC5789
197 <http://tools.ietf.org/html/rfc5789>, and is intended as a "partial
198 PUT" - sending just the changes required to the entity in question.
199 How this would be handled is down to your app, it will vary depending
200 on the type of entity in question and the serialization in use.
201
202 Dancer currently supports GET, PUT/PATCH, POST, DELETE, OPTIONS which
203 map to Retrieve, Update, Create, Delete respectively. Let's take a
204 look now at the "/add" route handler which handles a POST operation.
205
206 post '/add' => sub {
207 if ( not session('logged_in') ) {
208 send_error("Not logged in", 401);
209 }
210
211 my $db = connect_db();
212 my $sql = 'insert into entries (title, text) values (?, ?)';
213 my $sth = $db->prepare($sql) or die $db->errstr;
214 $sth->execute(params->{'title'}, params->{'text'}) or die $sth->errstr;
215
216 # note: 'flash' keyword imported by Dancer::Plugin::FlashMessage,
217 # not part of Dancer core
218 flash message => 'New entry posted!';
219
220 redirect '/';
221 };
222
223 As before, the HTTP verb begins the handler, followed by the route, and
224 a subroutine to do something - in this case, it will insert a new entry
225 into the database.
226
227 The first check in the subroutine is the make sure the user sending the
228 data is logged in. If not, the application sends back an error and
229 stops processing. Otherwise, we have standard DBI stuff. Let me insert
230 (heh, heh) a blatant plug here for always, always using parameterized
231 INSERTs in your application SQL statements. It's the only way to be
232 sure your application won't be vulnerable to SQL injection. (See
233 <http://www.bobby-tables.com> for correct INSERT examples in multiple
234 languages.) Here we're using the "params" convenience method to pull in
235 the parameters in the current HTTP request. (You can see the 'title'
236 and 'text' form parameters in the show_entries.tt template above.)
237 Those values are inserted into the database, then we set a flash
238 message for the user and redirect her back to the root URL.
239
241 Dancer comes with a simple in-memory session manager out of the box.
242 It supports a bunch of other session engines including YAML, memcached,
243 browser cookies and others. For this application we're going to stick
244 with the in-memory model which works great for development and
245 tutorials, but won't persist across server restarts or scale very well
246 in "real world" production scenarios.
247
248 Configuration options
249 To use sessions in our application, we have to tell Dancer to activate
250 the session handler and initialize a session manager. To do that, we
251 add some configuration directives toward the top of our dancr.pl file.
252 But there are more options than just the session engine we want to set.
253
254 set 'session' => 'Simple';
255 set 'template' => 'template_toolkit';
256 set 'logger' => 'console';
257 set 'log' => 'debug';
258 set 'show_errors' => 1;
259 set 'startup_info' => 1;
260 set 'warnings' => 1;
261 set 'database' => database;
262
263 Hopefully these are fairly self-explanatory. We want the Simple session
264 engine, the Template Toolkit template engine, logging enabled (at the
265 'debug' level with output to the console instead of a file), we want to
266 show errors to the web browser, log access attempts and log Dancer
267 warnings (instead of silently ignoring them)
268
269 In a more sophisticated application you would want to put these
270 configuration options into a YAML file, but for this tutorial, we're
271 going to keep it simple. Dancer also supports the notion of
272 application environments meaning you can create a configuration file
273 for your development instance, and another config file for the
274 production environment (with things like debugging and showing errors
275 disabled perhaps.) Dancer also doesn't impose any limits on what
276 parameters you can set using the "set" syntax. For this application
277 we're going to embed our single username and password into the
278 application itself.
279
280 set 'username' => 'admin';
281 set 'password' => 'password';
282
283 Hopefully no one will ever guess our clever password! Obviously, you
284 will want a more sophisticated user authentication scheme in any sort
285 of non-tutorial application but this is good enough for our purposes.
286
287 Logging in
288 Now that Dancr is configured to handle sessions, let's take a look at
289 the URL handler for the "/login" route.
290
291 any ['get', 'post'] => '/login' => sub {
292 my $err;
293
294 if ( request->method() eq "POST" ) {
295 # process form input
296 if ( params->{'username'} ne setting('username') ) {
297 $err = "Invalid username";
298 }
299 elsif ( params->{'password'} ne setting('password') ) {
300 $err = "Invalid password";
301 }
302 else {
303 session 'logged_in' => true;
304 set_flash('You are logged in.');
305 return redirect '/';
306 }
307 }
308
309 # display login form
310 template 'login.tt', {
311 'err' => $err,
312 };
313 };
314
315 This is the first handler which accepts two different verb types, a GET
316 for a human browsing to the URL and a POST for the browser to submit
317 the user's input to the web application. Since we're handling two
318 different verbs, we check to see what verb is in the request. If it's
319 not a POST, we drop down to the "template" directive and display the
320 login.tt template.
321
322 <h2>Login</h2>
323 <% IF err %><p class=error><strong>Error:</strong> <% err %><% END %>
324 <form action="<% login_url %>" method=post>
325 <dl>
326 <dt>Username:
327 <dd><input type=text name=username>
328 <dt>Password:
329 <dd><input type=password name=password>
330 <dd><input type=submit value=Login>
331 </dl>
332 </form>
333
334 This is even simpler than our show_entries.tt template - but wait -
335 there's a "login_url" template parameter and we're only passing in the
336 "err" parameter. Where's the missing parameter? It's being generated
337 and sent to the template in a "before_template_render" hook - we'll
338 come back to that in a moment or two.
339
340 So the user fills out the login.tt template and submits it back to the
341 "/login" route handler. We now check the user input against our
342 application settings and if they're incorrect, we alert the user,
343 otherwise the application starts a session and sets the "logged_in"
344 session parameter to the "true()" value. Dancer exports both a "true()"
345 and "false()" convenience method which we use here. After that, it's
346 another flash message and back to the root URL handler.
347
348 Logging out
349 And finally, we need a way to clear our user's session with the
350 customary logout procedure.
351
352 get '/logout' => sub {
353 session->destroy;
354 set_flash('You are logged out.');
355 redirect '/';
356 };
357
358 "session->destroy;" is Dancer's way to remove a stored session. We
359 notify the user she is logged out and route her back to the root URL
360 once again.
361
363 We still have a missing puzzle piece or two. First, how can we use
364 Dancer to serve our CSS stylesheet? Second, where are flash messages
365 displayed? Third, what about the "before_template_render" hook?
366
367 Serving static files
368 In Dancer, static files should go into the "public/" directory, but in
369 the application be sure to omit the "public/" element from the path.
370 For example, the stylesheet for Dancr lives in
371 "dancr/public/css/style.css" but is served from
372 <http://localhost:3000/css/style.css>.
373
374 If you wanted to build a mostly static web site you could simply write
375 route handlers like this one:
376
377 get '/' => sub {
378 send_file 'index.html';
379 };
380
381 where index.html would live in your "public/" directory.
382
383 "send_file" does exactly what it says: it loads a static file, then
384 sends the contents of that file to the user.
385
386 Layouts
387 I mentioned near the beginning of this tutorial that it is possible to
388 create a "layout" template. In Dancr, that layout is called "main" and
389 it's set up by putting in a directive like this:
390
391 set layout => 'main';
392
393 near the top of your web application. What this tells Dancer's
394 template engine is that it should look for a file called main.tt in
395 "dancr/views/layouts/" and insert the calls from the "template"
396 directive into a template parameter called "content".
397
398 For this web application, the layout template looks like this.
399
400 <!doctype html>
401 <html>
402 <head>
403 <title>Dancr</title>
404 <link rel=stylesheet type=text/css href="<% css_url %>">
405 </head>
406 <body>
407 <div class=page>
408 <h1>Dancr</h1>
409 <div class=metanav>
410 <% IF not session.logged_in %>
411 <a href="<% login_url %>">log in</a>
412 <% ELSE %>
413 <a href="<% logout_url %>">log out</a>
414 <% END %>
415 </div>
416 <% IF msg %>
417 <div class=flash> <% msg %> </div>
418 <% END %>
419 <% content %>
420 </div>
421 </body>
422 </html>
423
424 Aha! You now see where the flash message "msg" parameter gets rendered.
425 You can also see where the content from the specific route handlers is
426 inserted (the fourth line from the bottom in the "content" template
427 parameter.)
428
429 But what about all those other *_url template parameters?
430
431 Using "before_template_render"
432 Dancer has various hooks <http://en.wikipedia.org/wiki/Hooking> which
433 provide additional flexibility and power. The hooks available are
434 documented in the documentation for the hook keyword; the one we're
435 interested in here is "before_template_render" which provides a way to
436 manipulate the template parameters before they're passed to the engine
437 for processing.
438
439 Using this hook, we can generate and set the URIs for the "/login" and
440 "/logout" route handlers and the URI for the stylesheet. This is handy
441 for situations like this where there are values which are re-used
442 consistently across all (or most) templates. This cuts down on code-
443 duplication and makes your app easier to maintain over time since you
444 only need to update the values in this one place instead of everywhere
445 you render a template.
446
447 hook 'before_template_render' => sub {
448 my $tokens = shift;
449
450 $tokens->{'css_url'} = request->base . 'css/style.css';
451 $tokens->{'login_url'} = uri_for('/login');
452 $tokens->{'logout_url'} = uri_for('/logout');
453 };
454
455 Here again I'm using "uri_for" instead of hardcoding the routes. This
456 code block is executed before any of the templates are processed so
457 that the template parameters have the appropriate values before being
458 rendered.
459
461 The complete tutorial code is available on GitHub:
462
463 <https://github.com/PerlDancer/dancer-tutorial>
464
465 Assuming you have Git installed, you can clone the code:
466
467 git clone git://github.com/PerlDancer/dancer-tutorial.git
468
469 ... then run "dancer.pl".
470
472 There's a lot more to route matching than shown here. For example, you
473 can match routes with regular expressions, or you can match pieces of a
474 route like "/hello/:name" where the ":name" piece magically turns into
475 a named parameter in your handler for manipulation.
476
478 I hope this effort has been helpful and interesting enough to get you
479 exploring Dancer on your own. The framework is still under heavy
480 development but it's definitely mature enough to use in a production
481 project. Additionally, there are now a lot of great Dancer plugins
482 which extend and enhance the capabilities of the platform.
483
484 Happy dancing!
485
487 · <http://perldancer.org>
488
489 · <https://github.com/PerlDancer/Dancer>
490
491 · Dancer::Plugin
492
494 Copyright (C) 2010 by Mark R. Allen.
495
496 This is free software; you can redistribute it and/or modify it under
497 the terms of either the Artistic License 2.0 or the GNU Public License
498 version 2.
499
500 The CSS stylesheet is copied verbatim from the Flaskr example
501 application and is subject to their license:
502
503 Copyright (c) 2010 by Armin Ronacher and contributors.
504
505 Some rights reserved.
506
507 Redistribution and use in source and binary forms of the software as
508 well as documentation, with or without modification, are permitted
509 provided that the following conditions are met:
510
511 · Redistributions of source code must retain the above copyright
512 notice, this list of conditions and the following disclaimer.
513
514 · Redistributions in binary form must reproduce the above copyright
515 notice, this list of conditions and the following disclaimer in the
516 documentation and/or other materials provided with the
517 distribution.
518
519 · The names of the contributors may not be used to endorse or promote
520 products derived from this software without specific prior written
521 permission.
522
523 THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS
524 AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
525 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
526 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
527 NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
528 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
529 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
530 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
531 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
532 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
533 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF
534 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
535
537 Dancer Core Developers
538
540 This software is copyright (c) 2010 by Alexis Sukrieh.
541
542 This is free software; you can redistribute it and/or modify it under
543 the same terms as the Perl 5 programming language system itself.
544
545
546
547perl v5.32.0 2020-07-28 Dancer::Tutorial(3)