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.300004
10

Tutorial Overview

12       This tutorial is has three parts. Since they build on one another, each
13       part is meant to be gone through in sequential order.
14
15       Part I, the longest part of this tutorial, will focus on the basics of
16       Dancer2 development by building a simple yet functional blog app,
17       called "dancr", that you can use to impress your friends, mates, and
18       family.
19
20       In Part II, you'll learn about the preferred way to get your own web
21       apps up and running by using the "dancer2" utility. We will take the
22       script written in Part I and convert it into a proper Dancer2 app,
23       called "Dancr2", to help you gain an understanding of what the
24       "dancer2" utility does for you.
25
26       Finally, in Part III, we give you a taste of the power of plugins that
27       other developers have written and will show you how to modify the
28       "Dancr2" app to use a database plugin.
29
30       This tutorial assumes you have some familiarity with Perl and that you
31       know how to create and execute a Perl script on your computer. Some
32       experience with web development is also greatly helpful but not
33       entirely necessary. This tutorial is mostly geared toward developers
34       but website designers can get something out of it as well since the
35       basics of templating are covered plus it might be good for a designer
36       to have a decent idea of how Dancer2 works.
37

Part I: Let's Get Dancing!

39       Part I covers many of the basic concepts you'll need to know to lay a
40       good foundation for your future development work with Dancer2 by
41       building a simple micro-blogging app.
42
43   What is Dancer2?
44       Dancer2 is a micro-web framework, written in the Perl programming
45       language, and is modeled after a Ruby web application framework called
46       Sinatra <http://www.sinatrarb.com>.
47
48       When we say "micro" framework, we mean that Dancer2 aims to maximize
49       your freedom and control by getting out of your way. "Micro" doesn't
50       mean Dancer2 is only good for creating small apps. Instead, it means
51       that Dancer2's primary focus is on taking care of a lot of the boring,
52       technical details of your app for you and by creating an easy, clean
53       routing layer on top of your app's code. It also means you have almost
54       total control over the app's functionality and how you create and
55       present your content. You will not confined to someone else's approach
56       to creating a website or app.
57
58       With Dancer2, you can build anything from a specialized content
59       management system to providing a simple API for querying a database
60       over the web. But you don't have to reinvent the wheel, either. Dancer2
61       has hundreds of plugins that you can take advantage of. You can add
62       only the capabilities your app needs to keep complexity to a minimum.
63
64       As a framework, Dancer2 provides you with the tools and infrastructure
65       you can leverage to deliver content on the web quickly, easily and
66       securely. The tools, Dancer2 provides, called "keywords," are commands
67       that you use to build your app, access the data inside of it, and
68       deliver it on the internet in many different formats.
69
70       Dancer2's keywords provide what is called a Domain Specific Language
71       (DSL) designed specifically for the task of building apps. But don't
72       let the technical jargon scare you off. Things will become clearer in
73       our first code example which we will look at shortly.
74
75       Getting Dancer2 installed
76
77       First, we need to make sure you have Dancer2 installed. Typically, you
78       will do that with one of the following two commands:
79
80           cpan Dancer2  # requires the cpan command to be installed and configured
81           cpanm Dancer2 # requires you have cpanminus installed
82
83       If you aren't familiar with installing Perl modules on your machine,
84       you should read this guide <https://www.cpan.org/modules/INSTALL.html>.
85       You may also want to consult your OS's documentation or a knowledgeable
86       expert. And, of course, your search engine of choice is always there
87       for you, as well.
88
89       Your first Dancer2 "Hello World!" app
90
91       Now that you have Dancer2 installed, open up your favorite text editor
92       and copy and paste the following lines of Perl code into it and save it
93       to a file called "dancr.pl":
94
95           #!/usr/bin/env perl
96           use Dancer2;
97
98           get '/' => sub {
99               return 'Hello World!';
100           };
101
102           start;
103
104       If you make this script executable and run it, it will fire up a
105       simple, standalone web server that will display "Hello World!" when you
106       point your browser to <http://localhost:3000>. Cool!
107
108       Important note: We want to emphasize that writing a script file like
109       this with a "start" command is not how you would typically begin
110       writing a Dancer2 app. Part II of this tutorial will show you the
111       recommended approach using the "dancer2" utility. For now, we want to
112       stay focused on the fundamentals.
113
114       So, though our example app is very simple, there is a lot going on
115       under the hood when we invoke "use Dancer2;" in our first line of code.
116       We won't go into the gory details of how it all works. For now, it's
117       enough for you to know that the Dancer2 module infuses your script with
118       the ability to use Dancer2 keywords for building apps. Getting
119       comfortable with the concept of keywords is probably the most important
120       step you can take as a budding Dancer2 developer and this tutorial will
121       do its best to help foster your understanding of them.
122
123       The next line of code in our example (which spans three lines to make
124       it more readable) is the route handler. Let's examine this line
125       closely, because route handlers are at the core of how to build an app
126       with Dancer2.
127
128       The syntax of a Dancer2 "route handler" has three parts:
129
130       ·   an http method or http verb; in this example, we use the "get"
131           keyword to tell Dancer2 that this route should apply to GET http
132           requests. "get" is the first of many keywords that Dancer2 provides
133           that we will cover in this tutorial.  Those familiar with web
134           development will know that a GET request is what we use to fetch
135           information from a website.
136
137       ·   the route pattern; this is the bit of code that appears immediately
138           after our "get" keyword. In this example it is a forward slash
139           ("/"), wrapped in single quotes, and it represents the pattern we
140           wish to match against the URL that the browser, or client, has
141           requested. Web developers will immediately recognize that the
142           forward slash symbolizes the root directory of our website.
143           Experienced Perl programmers will pick up on the fact that the
144           route pattern is nothing more than an argument for our "get"
145           keyword.
146
147       ·   the route action; this is the subroutine that returns our data.
148           More precisely, it is a subroutine reference. The route action in
149           our example returns a simple string, "Hello World!". Like the route
150           pattern, the route action is nothing more than an argument to our
151           "get" keyword.
152
153           Note that convention has us use the fat comma ("=>") operator
154           between the route pattern and the action to to make our code more
155           readable. But we could just as well have used a regular old comma
156           to separate these argument to our "get" method. Gotta love Perl for
157           its flexibility.
158
159       So to put our route pattern in the example into plain English, we are
160       telling our app, "If the root directory is requested with the GET http
161       method, send the string 'Hello World!' back in our response." Of
162       course, since this is a web app, we also have to send back headers with
163       our response. This is quitely taken care of for us by Dancer2 so we
164       don't have to think about it.
165
166       The syntax for route handlers might seem a bit foreign for newer Perl
167       developers.  But rest assured there is nothing magical about it and it
168       is all just plain old Perl under the hood. If you keep in mind that the
169       keyword is a subroutine (or more precisely, a method) and that the
170       pattern and action are arguments to the keyword, you'll pick it up in
171       no time. Thinking of these keywords as "built-ins" to the Dancer2
172       framework might also eliminate any initial confusion about them.
173
174       The most important takeaway here is that we build our app by adding
175       route handlers which are nothing more than a collection of, HTTP verbs,
176       URL patterns, and actions.
177
178   How about a little more involved example?
179       While investigating some Python web frameworks like Flask
180       <http://flask.pocoo.org/> or Bottle <https://bottlepy.org/docs/dev/>, I
181       enjoyed the way they explained step-by-step how to build an example
182       application which was a little more involved than a trivial example.
183       This tutorial is modeled after them.
184
185       Using the Flaskr <https://github.com/pallets/flask> sample application
186       as my inspiration (OK, shamelessly plagiarised) I translated that
187       application to the Dancer2 framework so I could better understand how
188       Dancer2 worked. (I'm learning it too!)
189
190       So "dancr" was born.
191
192       dancr is a simple "micro" blog which uses the SQLite
193       <http://www.sqlite.org> database engine for simplicity's sake.  You'll
194       need to install sqlite on your server if you don't have it installed
195       already. Consult your OS documentation for getting SQLite installed on
196       your machine.
197
198       Required Perl modules
199
200       Obviously you need Dancer2 installed. You'll also need the Template
201       Toolkit, File::Slurper, and DBD::SQLite modules.  These all can be
202       installed using your CPAN client with the following command:
203
204           cpan Template File::Slurper DBD::SQLite
205
206   The database code
207       We're not going to spend a lot of time on the database, as it's not
208       really the point of this particular tutorial. Try not to dwell on this
209       section too much if you don't understand all of it.
210
211       Open your favorite text editor <http://www.vim.org> and create a schema
212       definition called 'schema.sql' with the following content:
213
214           create table if not exists entries (
215               id integer primary key autoincrement,
216               title string not null,
217               text string not null
218           );
219
220       Here we have a single table with three columns: id, title, and text.
221       The 'id' field is the primary key and will automatically get an ID
222       assigned by the database engine when a row is inserted.
223
224       We want our application to initialize the database automatically for us
225       when we start it. So, let's edit the 'dancr.pl' file we created earlier
226       and give it the ability to talk to our database with the following
227       subroutines: (Or, if you prefer, you can copy and paste the finished
228       dancr.pl script, found near the end of Part I in this tutorial, into
229       the file all at once and then just follow along with the tutorial.)
230
231           sub connect_db {
232               my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
233                   or die $DBI::errstr;
234
235               return $dbh;
236           }
237
238           sub init_db {
239               my $db     = connect_db();
240               my $schema = read_text('./schema.sql');
241               $db->do($schema)
242                   or die $db->errstr;
243           }
244
245       Nothing too fancy in here, I hope. It's standard DBI except for the
246       "setting('database')" thing, more on that in a bit. For now, just
247       assume that the expression evaluates to the location of the database
248       file.
249
250       In Part III of the tutorial, we will show you how to use the
251       Dancer2::Plugin::Database module for an easier way to configure and
252       manage database connections for your Dancer2 apps.
253
254   Our first route handler
255       Ok, let's get back to the business of learning Dancer2 by creating our
256       app's first route handler for the root URL.  Replace the route handler
257       in our simple example above with this one:
258
259           get '/' => sub {
260               my $db  = connect_db();
261               my $sql = 'select id, title, text from entries order by id desc';
262
263               my $sth = $db->prepare($sql)
264                   or die $db->errstr;
265
266               $sth->execute
267                   or die $sth->errstr;
268
269               template 'show_entries.tt', {
270                   msg           => get_flash(),
271                   add_entry_url => uri_for('/add'),
272                   entries       => $sth->fetchall_hashref('id'),
273               };
274           };
275
276       Our new route handler is the same as the one in our first example
277       except that our route action does a lot more work.
278
279       Something you might not have noticed right away is the semicolon at the
280       end of the route handler. This might confuse newer Perl coders and is a
281       source of bugs for more experienced ones who forget to add it. We need
282       the semicolon there because we are creating a reference to a subroutine
283       and because that's just what the Perl compiler demands and we must obey
284       if we want our code to run.
285
286       Alright, let's take a closer look at this route's action. The first few
287       lines are standard DBI. The important bit related to Dancer2 is the
288       "template" keyword at the end of the action. That tells Dancer2 to
289       process the output through one of its templating engines. There are
290       many template engines available for use with Dancer2. In this tutorial,
291       we're using Template Toolkit which offers a lot more flexibility than
292       the simple default Dancer2 template engine.
293
294       Templates all go into a "views/" directory which located in the same
295       directory as our dancr.pl script. Optionally, you can create a "layout"
296       template which provides a consistent look and feel for all of your
297       views. We'll construct our own layout template, cleverly named main.tt,
298       a little later in this tutorial.
299
300       So what's going on with the hashref as the second argument to the
301       template directive? Those are all of the parameters we want to pass
302       into our template. We have a "msg" field which displays a message to
303       the user when an event happens like a new entry is posted, or the user
304       logs in or out.  It's called a "flash" message because we only want to
305       display it one time, not every time the "/" URL is rendered.
306
307       The "uri_for" directive tells Dancer2 to provide a URI for that
308       specific route, in this case, it is the route to post a new entry into
309       the database.  You might ask why we don't simply hardcode the "/add"
310       URI in our application or templates.  The best reason not to do that is
311       because it removes a layer of flexibility as to where to "mount" the
312       web application.  Although the application is coded to use the root URL
313       "/" it might be better in the future to locate it under its own URL
314       route (maybe "/dancr"?)  - at that point we'd have to go through our
315       application and the templates and update the URLs and hope we didn't
316       miss any of them.  By using the "uri_for" Dancer2 method, we can easily
317       load the application wherever we like and not have to modify the
318       application at all.
319
320       Finally, the "entries" field contains a hashref with the results from
321       our database query.  Those results will be rendered in the template
322       itself, so we just pass them in.
323
324       So what does the show_entries.tt template look like? This:
325
326         [% IF session.logged_in %]
327           <form action="[% add_entry_url %]" method=post class=add-entry>
328             <dl>
329               <dt>Title:
330               <dd><input type=text size=30 name=title>
331               <dt>Text:
332               <dd><textarea name=text rows=5 cols=40></textarea>
333               <dd><input type=submit value=Share>
334             </dl>
335           </form>
336         [% END %]
337         <ul class=entries>
338         [% IF entries.size %]
339           [% FOREACH id IN entries.keys.nsort %]
340             <li><h2>[% entries.$id.title | html %]</h2>[% entries.$id.text | html %]
341           [% END %]
342         [% ELSE %]
343           <li><em>Unbelievable. No entries here so far</em>
344         [% END %]
345         </ul>
346
347       Go ahead and create a "views/" directory in the same directory as the
348       script and add this file to it.
349
350       Again, since this isn't a tutorial about Template Toolkit, we'll gloss
351       over the syntax here and just point out the section which starts with
352       "<ul class=entries>". This is the section where the database query
353       results are displayed. You can also see at the very top some discussion
354       about a session, more on that soon.
355
356       The only other Template Toolkit related thing that has to be mentioned
357       here is the "| html" in "[% entries.$id.title | html %]". That's a
358       filter <http://www.template-
359       toolkit.org/docs/manual/Filters.html#section_html> to convert
360       characters like "<" and ">" to "&lt;" and "&gt;". This way they will be
361       displayed by the browser as content on the page rather than just
362       included. If we did not do this, the browser might interpret content as
363       part of the page, and a malicious user could smuggle in all kinds of
364       bad code that would then run in another user's browser. This is called
365       Cross Site Scripting <https://en.wikipedia.org/wiki/Cross-
366       site_scripting> or XSS and you should make sure to avoid it by always
367       filtering data that came in from the web when you display it in a
368       template.
369
370   Other HTTP verbs
371       There are 8 defined HTTP verbs defined in RFC 2616
372       <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9>: OPTIONS,
373       GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT.  Of these, the majority
374       of web applications focus on the verbs which closely map to the CRUD
375       (Create, Retrieve, Update, Delete) operations most database-driven
376       applications need to implement.
377
378       In addition, the "PATCH" verb was defined in RFC5789
379       <http://tools.ietf.org/html/rfc5789>, and is intended as a "partial
380       PUT", sending just the changes required to the entity in question.  How
381       this would be handled is down to your app, it will vary depending on
382       the type of entity in question and the serialization in use.
383
384       Dancer2's keywords currently supports GET, PUT/PATCH, POST, DELETE,
385       OPTIONS which map to Retrieve, Update, Create, Delete respectively.
386       Let's take a look now at the "/add" route handler which handles a POST
387       operation.
388
389           post '/add' => sub {
390               if ( not session('logged_in') ) {
391                   send_error("Not logged in", 401);
392               }
393
394               my $db = connect_db();
395               my $sql = 'insert into entries (title, text) values (?, ?)';
396               my $sth = $db->prepare($sql)
397                   or die $db->errstr;
398
399               $sth->execute(
400                   body_parameters->get('title'),
401                   body_parameters->get('text')
402               ) or die $sth->errstr;
403
404               set_flash('New entry posted!');
405               redirect '/';
406           };
407
408       As before, the HTTP verb begins the handler, followed by the route, and
409       a subroutine to do something; in this case it will insert a new entry
410       into the database.
411
412       The first check in the subroutine is to make sure the user sending the
413       data is logged in. If not, the application returns an error and stops
414       processing. Otherwise, we have standard DBI stuff. Let me insert (heh,
415       heh) a blatant plug here for always, always using parameterized INSERTs
416       in your application SQL statements. It's the only way to be sure your
417       application won't be vulnerable to SQL injection. (See
418       <http://www.bobby-tables.com> for correct INSERT examples in multiple
419       languages.) Here we're using the "body_parameters" convenience method
420       to pull in the parameters in the current HTTP request. (You can see the
421       'title' and 'text' form parameters in the show_entries.tt template
422       above.) Those values are inserted into the database, then we set a
423       flash message for the user and redirect her back to the root URL.
424
425       It's worth mentioning that the "flash message" is not part of Dancer2,
426       but a part of this specific application. We need to implement it
427       ourself.
428
429   Logins and sessions
430       Dancer2 comes with a simple in-memory session manager out of the box.
431       It supports a bunch of other session engines including YAML, memcached,
432       browser cookies and others. We'll just stick with the in-memory model
433       which works great for development and tutorials, but won't persist
434       across server restarts or scale very well in "real world" production
435       scenarios.
436
437       Configuration options
438
439       To use sessions in our application, we have to tell Dancer2 to activate
440       the session handler and initialize a session manager. To do that, we
441       add some configuration directives toward the top of our 'dancr.pl'
442       file.  But there are more options than just the session engine we want
443       to set.
444
445           set 'database'     => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
446           set 'session'      => 'Simple';
447           set 'template'     => 'template_toolkit';
448           set 'logger'       => 'console';
449           set 'log'          => 'debug';
450           set 'show_errors'  => 1;
451           set 'startup_info' => 1;
452           set 'warnings'     => 1;
453
454       Hopefully these are fairly self-explanatory. We want the Simple session
455       engine, the Template Toolkit template engine, logging enabled (at the
456       'debug' level with output to the console instead of a file), we want to
457       show errors to the web browser, log access attempts and log Dancer2
458       warnings (instead of silently ignoring them).
459
460       Dancer2 doesn't impose any limits on what parameters you can set using
461       the "set" syntax. For this application we're going to embed our single
462       username and password into the application itself:
463
464           set 'username' => 'admin';
465           set 'password' => 'password';
466
467       Hopefully no one will ever guess our clever password!  Obviously, you
468       will want a more sophisticated user authentication scheme in any sort
469       of non-tutorial application but this is good enough for our purposes.
470
471       In Part II of our tutorial, we will show you how to use Dancer2's
472       configuration files to manage these options and set up different
473       environments for your app using different configuration files. For now,
474       we're going to keep it simple and leave that discussion for later.
475
476       Logging in
477
478       Now that dancr is configured to handle sessions, let's take a look at
479       the URL handler for the "/login" route.
480
481           any ['get', 'post'] => '/login' => sub {
482               my $err;
483
484               if ( request->method() eq "POST" ) {
485                   # process form input
486                   if ( body_parameters->get('username') ne setting('username') ) {
487                       $err = "Invalid username";
488                   }
489                   elsif ( body_parameters->get('password') ne setting('password') ) {
490                       $err = "Invalid password";
491                   }
492                   else {
493                       session 'logged_in' => true;
494                       set_flash('You are logged in.');
495                       return redirect '/';
496                   }
497               }
498
499               # display login form
500               template 'login.tt', {
501                   err => $err,
502               };
503           };
504
505       This is the first handler which accepts two different verb types, a GET
506       for a human browsing to the URL and a POST for the browser to submit
507       the user's input to the web application.  Since we're handling two
508       different verbs, we check to see what verb is in the request.  If it's
509       not a POST, we drop down to the "template" directive and display the
510       login.tt template:
511
512         <h2>Login</h2>
513         [% IF err %]<p class=error><strong>Error:</strong> [% err %][% END %]
514         <form action="[% login_url %]" method=post>
515           <dl>
516             <dt>Username:
517             <dd><input type=text name=username>
518             <dt>Password:
519             <dd><input type=password name=password>
520             <dd><input type=submit value=Login>
521           </dl>
522         </form>
523
524       This is even simpler than our show_entries.tt template–but wait–
525       there's a "login_url" template parameter and we're only passing in the
526       "err" parameter. Where's the missing parameter? It's being generated
527       and sent to the template in a "before_template_render" directive, we'll
528       come back to that in a moment or two.
529
530       So the user fills out the login.tt template and submits it back to the
531       "/login" route handler.  We now check the user input against our
532       application settings and if the input is incorrect, we alert the user,
533       otherwise the application starts a session and sets the "logged_in"
534       session parameter to the "true()" value. Dancer2 exports both a
535       "true()" and "false()" convenience method which we use here.  After
536       that, it's another flash message and back to the root URL handler.
537
538       Logging out
539
540       And finally, we need a way to clear our user's session with the
541       customary logout procedure.
542
543           get '/logout' => sub {
544               app->destroy_session;
545               set_flash('You are logged out.');
546               redirect '/';
547           };
548
549       "app->destroy_session;" is Dancer2's way to remove a stored session.
550       We notify the user she is logged out and route her back to the root URL
551       once again.
552
553       You might wonder how we can then set a value in the session in
554       "set_flash", because we just destroyed the session.
555
556       Destroying the session has removed the data from the persistence layer
557       (which is the memory of our running application, because we are using
558       the "simple" session engine). If we write to the session now, it will
559       actually create a completely new session for our user. This new, empty
560       session will have a new session ID, which Dancer2 tells the user's
561       browser about in the response.  When the browser requests the root URL,
562       it will send this new session ID to our application.
563
564   Layout and static files
565       We still have a missing puzzle piece or two. First, how can we use
566       Dancer2 to serve our CSS stylesheet? Second, where are flash messages
567       displayed?  Third, what about the "before_template_render" directive?
568
569       Serving static files
570
571       In Dancer2, static files should go into the "public/" directory, but in
572       the application itself be sure to omit the "public/" element from the
573       path.  For example, the stylesheet for dancr lives in
574       "dancr/public/css/style.css" but is served from
575       <http://localhost:3000/css/style.css>.
576
577       If you wanted to build a mostly static web site you could simply write
578       route handlers like this one:
579
580           get '/' => sub {
581               send_file 'index.html';
582           };
583
584       where index.html would live in your "public/" directory.
585
586       "send_file" does exactly what it says: it loads a static file, then
587       sends the contents of that file to the user.
588
589       Let's go ahead and create our style sheet. In the same directory as
590       your dancr.pl script, issue the following commands:
591
592           mkdir public && mkdir public/css && touch public/css/style.css
593
594       Next add the following css to the "public/css/style.css" file you just
595       created:
596
597           body            { font-family: sans-serif; background: #eee; }
598           a, h1, h2       { color: #377ba8; }
599           h1, h2          { font-family: 'Georgia', serif; margin: 0; }
600           h1              { border-bottom: 2px solid #eee; }
601           h2              { font-size: 1.2em; }
602
603           .page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
604                             padding: 0.8em; background: white; }
605           .entries        { list-style: none; margin: 0; padding: 0; }
606           .entries li     { margin: 0.8em 1.2em; }
607           .entries li h2  { margin-left: -1em; }
608           .add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
609           .add-entry dl   { font-weight: bold; }
610           .metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
611                             margin-bottom: 1em; background: #fafafa; }
612           .flash          { background: #cee5F5; padding: 0.5em;
613                             border: 1px solid #aacbe2; }
614           .error          { background: #f0d6d6; padding: 0.5em; }
615
616       Be sure to save the file.
617
618       Layouts
619
620       I mentioned earlier in the tutorial that it is possible to create a
621       "layout" template. In dancr, that layout is called "main" and it's set
622       up by putting in a directive like this:
623
624           set layout => 'main';
625
626       near the top of your web application.  This tells Dancer2's template
627       engine that it should look for a file called main.tt in
628       "views/layouts/" and insert the calls from the "template" directive
629       into a template parameter called "content".
630
631       Here is the simple layout file we will use for this web application. Go
632       ahead and add this the main.tt file to the "views/layouts/" directory.
633
634         <!doctype html>
635         <html>
636         <head>
637           <title>dancr</title>
638           <link rel=stylesheet type=text/css href="[% css_url %]">
639         </head>
640         <body>
641           <div class=page>
642           <h1>dancr</h1>
643              <div class=metanav>
644              [% IF not session.logged_in %]
645                <a href="[% login_url %]">log in</a>
646              [% ELSE %]
647                <a href="[% logout_url %]">log out</a>
648              [% END %]
649           </div>
650           [% IF msg %]
651             <div class=flash> [% msg %] </div>
652           [% END %]
653           [% content %]
654         </div>
655         </body>
656         </html>
657
658       Aha! You now see where the flash message "msg" parameter gets rendered.
659       You can also see where the content from the specific route handlers is
660       inserted (the fourth line from the bottom in the "content" template
661       parameter).
662
663       But what about all those other *_url template parameters?
664
665       Using "before_template_render"
666
667       Dancer2 has a way to manipulate the template parameters before they're
668       passed to the engine for processing. It's "before_template_render".
669       Using this keyword, you can generate and set the URIs for the "/login"
670       and "/logout" route handlers and the URI for the stylesheet. This is
671       handy for situations like this where there are values which are re-used
672       consistently across all (or most) templates. This cuts down on code-
673       duplication and makes your app easier to maintain over time since you
674       only need to update the values in this one place instead of everywhere
675       you render a template.
676
677           hook before_template_render => sub {
678               my $tokens = shift;
679
680               $tokens->{'css_url'}    = request->base . 'css/style.css';
681               $tokens->{'login_url'}  = uri_for('/login');
682               $tokens->{'logout_url'} = uri_for('/logout');
683           };
684
685       Here again I'm using "uri_for" instead of hardcoding the routes.  This
686       code block is executed before any of the templates are processed so
687       that the template parameters have the appropriate values before being
688       rendered.
689
690   Putting it all together
691       Here's the complete 'dancr.pl' script from start to finish.
692
693           use Dancer2;
694           use DBI;
695           use File::Spec;
696           use File::Slurper qw/ read_text /;
697           use Template;
698
699           set 'database'     => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
700           set 'session'      => 'Simple';
701           set 'template'     => 'template_toolkit';
702           set 'logger'       => 'console';
703           set 'log'          => 'debug';
704           set 'show_errors'  => 1;
705           set 'startup_info' => 1;
706           set 'warnings'     => 1;
707           set 'username'     => 'admin';
708           set 'password'     => 'password';
709           set 'layout'       => 'main';
710
711           sub set_flash {
712               my $message = shift;
713
714               session flash => $message;
715           }
716
717           sub get_flash {
718               my $msg = session('flash');
719               session->delete('flash');
720
721               return $msg;
722           }
723
724           sub connect_db {
725               my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
726                   or die $DBI::errstr;
727
728               return $dbh;
729           }
730
731           sub init_db {
732               my $db     = connect_db();
733               my $schema = read_text('./schema.sql');
734               $db->do($schema)
735                   or die $db->errstr;
736           }
737
738           hook before_template_render => sub {
739               my $tokens = shift;
740
741               $tokens->{'css_url'}    = request->base . 'css/style.css';
742               $tokens->{'login_url'}  = uri_for('/login');
743               $tokens->{'logout_url'} = uri_for('/logout');
744           };
745
746           get '/' => sub {
747               my $db  = connect_db();
748               my $sql = 'select id, title, text from entries order by id desc';
749
750               my $sth = $db->prepare($sql)
751                   or die $db->errstr;
752
753               $sth->execute
754                   or die $sth->errstr;
755
756               template 'show_entries.tt', {
757                   msg           => get_flash(),
758                   add_entry_url => uri_for('/add'),
759                   entries       => $sth->fetchall_hashref('id'),
760               };
761           };
762
763           post '/add' => sub {
764               if ( not session('logged_in') ) {
765                   send_error("Not logged in", 401);
766               }
767
768               my $db  = connect_db();
769               my $sql = 'insert into entries (title, text) values (?, ?)';
770
771               my $sth = $db->prepare($sql)
772                   or die $db->errstr;
773
774               $sth->execute(
775                   body_parameters->get('title'),
776                   body_parameters->get('text')
777               ) or die $sth->errstr;
778
779               set_flash('New entry posted!');
780               redirect '/';
781           };
782
783           any ['get', 'post'] => '/login' => sub {
784               my $err;
785
786               if ( request->method() eq "POST" ) {
787                   # process form input
788                   if ( body_parameters->get('username') ne setting('username') ) {
789                       $err = "Invalid username";
790                   }
791                   elsif ( body_parameters->get('password') ne setting('password') ) {
792                       $err = "Invalid password";
793                   }
794                   else {
795                       session 'logged_in' => true;
796                       set_flash('You are logged in.');
797                       return redirect '/';
798                   }
799               }
800
801               # display login form
802               template 'login.tt', {
803                   err => $err,
804               };
805
806           };
807
808           get '/logout' => sub {
809               app->destroy_session;
810               set_flash('You are logged out.');
811               redirect '/';
812           };
813
814           init_db();
815           start;
816
817       Advanced route moves
818
819       There's a lot more to route matching than shown here. For example, you
820       can match routes with regular expressions, or you can match pieces of a
821       route like "/hello/:name" where the ":name" piece magically turns into
822       a named parameter in your handler for manipulation.
823
824       You can explore this and other advanced concepts by reading the
825       Dancer2::Manual.
826

Part II: Taking Advantage of the "dancer2" Utility to Set Up New Apps

828       In Part I, we took an ordinary Perl script and turned it into a simple
829       web app to teach you basic Dancer2 concepts. While starting with a
830       simple script like this helped make it easier to teach these concepts,
831       it did not demonstrate how a typical app is built by a Dancer2
832       developer. So let's show you how things really get done.
833
834   Creating a new app
835       So now that you have a better idea of what goes into building an app
836       with Dancer2, it's time to cha-cha with the "dancer2" utility which
837       will save you a lot of time and effort by setting up directories,
838       files, and default configuration settings for you.
839
840       The "dancer2" utility was installed on your machine when you installed
841       the Dancer2 distribution. Hop over to the command line into a directory
842       you have permission to write to and issue the following command:
843
844           dancer2 -a Dancr2
845
846       That command should output something like the following to the console:
847
848           + Dancr2
849           + Dancr2/config.yml
850           + Dancr2/Makefile.PL
851           + Dancr2/MANIFEST.SKIP
852           + Dancr2/.dancer
853           + Dancr2/cpanfile
854           + Dancr2/bin
855           + Dancr2/bin/app.psgi
856           + Dancr2/environments
857           + Dancr2/environments/development.yml
858           + Dancr2/environments/production.yml
859           + Dancr2/lib
860           + Dancr2/lib/Dancr2.pm
861           + Dancr2/public
862           + Dancr2/public/favicon.ico
863           + Dancr2/public/500.html
864           + Dancr2/public/dispatch.cgi
865           + Dancr2/public/404.html
866           + Dancr2/public/dispatch.fcgi
867           + Dancr2/public/css
868           + Dancr2/public/css/error.css
869           + Dancr2/public/css/style.css
870           + Dancr2/public/images
871           + Dancr2/public/images/perldancer.jpg
872           + Dancr2/public/images/perldancer-bg.jpg
873           + Dancr2/public/javascripts
874           + Dancr2/public/javascripts/jquery.js
875           + Dancr2/t
876           + Dancr2/t/001_base.t
877           + Dancr2/t/002_index_route.t
878           + Dancr2/views
879           + Dancr2/views/index.tt
880           + Dancr2/views/layouts
881           + Dancr2/views/layouts/main.tt
882
883       What you just did was create a fully functional app in Dancer2 with
884       just one command! The new app, named "Dancr2," won't do anything
885       particularly useful until you add your own routes to it, but it does
886       take care of many of the tedious tasks of setting up an app for you.
887
888       The files and folders that were generated and that you see listed above
889       provide a convenient scaffolding, or skeleton, upon which you can build
890       your app. The default skelelton provides you with basic error pages,
891       css, javascript, graphics, tests, templates and other files which you
892       are free to modify and customize to your liking.
893
894       If you don't like the default skeleton provided to you by Dancer, the
895       "dancer2" command allows you to generate your own custom skeletons.
896       Consult "BOOTSTRAPPING_A_NEW_APP" in Dancer2::Manual for further
897       details on this and other capabilities of the "dancer2") utility.
898
899   Getting the new app up and running with Plack
900       In Part I, we used the "start" command in our script to launch a server
901       to serve our app. Things are a little different when using "dancer2",
902       however. You'll notice that the "dancer2" utility created a "bin/"
903       directory with a file in it called "app.psgi". This is the file we use
904       to get our app up and running.
905
906       Let's see how to to do that by first changing into the Dancr2 directory
907       and then starting the server using the "plackup" command:
908
909           cd Dancr2;
910           plackup -p 5000 bin/app.psgi
911
912       If all went well, you'll be able to see the Dancr2 home page by
913       visiting:
914
915           http://localhost:5000
916
917       The web page you see there gives you some very basic advice for tuning
918       and modifying your app and where you can go for more information to
919       learn about developing apps with Dancer2 (like this handy tutorial!).
920
921       Our Dancr2 app is served on a simple web server provided by Plack.
922       Plack is PSGI compliant software, hence the "psgi" extension for our
923       file in the "bin/" directory. Plack and PSGI is beyond the scope of
924       this tutorial but you can learn more by visiting the Plack website
925       <http://plackperl.org/>.
926
927       For now, all you need to know is that if you are deploying an app for
928       use by just yourself or a handful of people on a local network, Plack
929       alone may do the trick. More typically, you would use Plack in
930       conjunction with other server software to make your app much more
931       robust. But in the early stages of your app's development, a simple
932       Plack server is more than likely all you need.
933
934       To learn more about the different ways for deploying your app, see the
935       Dancer2 Deployment Manual
936
937   Porting dancr.pl over to the new Dancr2 app
938       Ok, so now that we've got our new Dancr2 app up and running, it's time
939       to learn how to take advantage of what the "dancer2" utility set up for
940       us by porting our dancr.pl script created in Part I into Dancr2.
941
942       The "lib/" directory
943
944       The "lib/" directory in our Dancr2 app is where our "app.psgi" file
945       will expect our code to live. So let's take a peek at the file
946       generated for us in there:
947
948           cat lib/Dancr2.pm
949
950       You'll see something like the following bit of code which provides a
951       single route to our app's home page and loads the index template:
952
953           package Dancr2;
954           use Dancer2;
955
956           our $VERSION = '0.1';
957
958           get '/' => sub {
959               template 'index' => { title => 'Dancr2' };
960           };
961
962           true;
963
964       The first thing you'll notice is that instead of a script, we are using
965       a module, "Dancr2" to package our code. Modules make it easer to pull
966       off many powerful tricks like packaging our app across several discrete
967       modules. We'll let the manual explain this more advanced technique.
968
969       Updating the Dancr2 module
970
971       Now that we know where to put our code, let's update the "Dancr2.pm"
972       module with our original "dancr.pl" code. Remove the existing sample
973       route in "Dancr2.pm" and replace it with the code from our "dancr.pl"
974       file. You'll have to make a couple of adjustments to the "dancr.pl"
975       code like removing the "use Dancer2;" line since it's already provided
976       by our module. You'll also want to be sure to remove the "start;" line
977       as well from the end of the file.
978
979       When you're done, "Dancr2.pm" should look something close to this:
980
981           package Dancr2;
982           use Dancer2;
983
984           our $VERSION = '0.1';
985
986           # Our original dancr.pl code with some minor tweaks
987           use DBI;
988           use File::Spec;
989           use File::Slurper qw/ read_text /;
990           use Template;
991
992           set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
993           set 'session'  => 'YAML';
994           ...
995
996           <snip> # The rest of the stuff </snip>
997
998           ...
999
1000           sub init_db {
1001               my $schema = read_text('./schema.sql');
1002               $db->do($schema)
1003                   or die $db->errstr;
1004           }
1005
1006           get '/logout' => sub {
1007               app->destroy_session;
1008               set_flash('You are logged out.');
1009               redirect '/';
1010           };
1011
1012           init_db();
1013
1014       Finally, to avoid getting an error in the "init_db") subroutine when it
1015       tries to load our schema file, copy over the "schema.db" file to the
1016       root directory of the Dancr2 app:
1017
1018           cp /path/to/dancr.pl/schema.db /path/to/Dancr2;
1019
1020       Ok, now that we've got the code moved over, let's move the assets from
1021       dancr.pl to our new app.
1022
1023       The "public/" directory
1024
1025       As mentioned in Part I, our static assets go into our "public/"
1026       directory. If you followed along with the tutorial in Part I, you
1027       should have a "public/" directory with a "public/css" subdirectory and
1028       a file called "style.css" within that.
1029
1030       Dancer2 has conveniently generated the "public/css" directory for us
1031       which has a default css file. Let's copy the style sheet from our
1032       original app so our new app can use it:
1033
1034           # Note: This command overwrites the default style sheet. Move it or copy
1035           # it if you wish to preserve it.
1036
1037           cp /path/to/dancr.pl/public/css/style.css /path/to/Dancr2/public/css;
1038
1039       The "views" directory
1040
1041       Along with our "public/" directory, Dancer has also provided a "views/"
1042       directory, which as we covered, serves as the a home for our templates.
1043       Let's get those copied over now:
1044
1045           # NOTE: This command will overwrite the default main.tt tempalte file. Move
1046           # it or copy it if you wish to preserve it.
1047
1048           cp -r /path/to/dancr.pl/views/* /path/to/Dancr2/views;
1049
1050       Does it work?
1051
1052       If you followed the instructions here closely, your Dancr2 app should
1053       be working.  Shut down any running Plack servers and then issue the
1054       same plackup command to see if it runs:
1055
1056           cd /path/to/Dancr2
1057           plackup -p 5000 bin/app.psgi
1058
1059       If you see any errors, get them resolved until the app loads.
1060
1061   Configuring Your App
1062       In Part I, you configured your app with a series of "set" statements
1063       near the top of your file. Now we will show you a better way to
1064       configure your app using Dancer2's configuration files.
1065
1066       Your skeleton provides your app with three different configuration
1067       files. The first two files we'll discuss, found in the "environments/"
1068       folder of your app, are "development.yml" and "production.yml". As you
1069       can probably guess, the "development.yml" file has settings intended to
1070       be used while developing the app. The "production.yml" file has
1071       settings more appropriate for running your app when used by others. The
1072       third configuration file is found in the root directory of your app and
1073       is named "config.yml". This file has the settings that are common to
1074       all environments but that can be overridden by the environment
1075       configuration files. You can still override any configuration file
1076       settings in your modules using the "set" command.
1077
1078       We will take a look at the "development.yml" file first. Open that file
1079       in your text editor and take a look inside. It has a bunch of helpful
1080       comments and the following five settings sprinkled throughout:
1081
1082           logger: "console"
1083           log: "core"
1084           warnings: 1
1085           show_errors: 1
1086           startup_info: 1
1087
1088       The first four settings duplicate many of the settings in our new
1089       Dancr2 app. So in the spirit of DRY (don't repeat yourself), edit your
1090       Dancr2 module and delete the four lines that correspond to these four
1091       settings.
1092
1093       Then, in the configuration file, be sure to change the value for the
1094       "log" setting from "core" to "debug" so it matches the value we had in
1095       our module.
1096
1097       We will leave it up to you what you want to do with the fifth setting,
1098       "startup_info". You can read about that setting, along with all the
1099       other settings, in the configuration manual.
1100
1101       Finally, let's add a new setting to the configuration file for
1102       "session" with the following line:
1103
1104           session: "Simple"
1105
1106       Then delete the corresponding setting from your Dancr2 module.
1107
1108       Alright, our Dancr2 app is a little leaner and meaner. Now open the
1109       main "config.yml" file and look for the settings in there that are also
1110       duplicated in our app's module. There are two:
1111
1112           layout: "main"
1113           template: "simple"
1114
1115       Leave "layout" as is but change the template setting to
1116       "template_toolkit".  Then edit your Dancr2 module file and delete these
1117       two settings.
1118
1119       Finally, add the following configuration settings to the .yml file:
1120
1121           username: "admin"
1122           password: "password"
1123
1124       Then you delete these two settings from the Dancr2 module, as well.
1125
1126       So, if you have been following along, you now have only the following
1127       "set" command in your Dancr2 module, related to the database
1128       configuration:
1129
1130           set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
1131
1132       We will get rid of this setting in Part III of the tutorial. All the
1133       rest of the settings have been transferred to our configuration files.
1134       Nice!
1135
1136       We still have a little more cleanup we can do. Now that Dancer2 knows
1137       we are using Template::Toolkit, we can delete the "use Template;" line
1138       from our module.
1139
1140       Now start the app "plackup" command and check to see that everything
1141       works. By default, Dancer2 will load the development environment
1142       configuration. When it comes time to put your app into production, you
1143       can load the "production.yml" file configuration with plackup's "--env"
1144       switch like so:
1145
1146           plackup -p 5000 --env production bin/app.psgi
1147
1148   Keep on Dancing!
1149       This concludes Part II of our tutorial where we showed you how to take
1150       advantage of the "dancer2" utility to set up a app skeleton to make it
1151       really easy to get started developing your own apps.
1152
1153       Part III will refine our app a little further by showing you how to use
1154       plugins so you can start capitalizing on all the great work contributed
1155       by other Dancer2 developers.
1156

Part III: Plugins, Your Many Dancing Partners

1158       Dancer2 takes advantage of the open source software revolution by
1159       making it exceedingly easy to use plugins that you can mix into your
1160       app to give it new functionality. In Part III of this tutorial, we will
1161       update our new Dancr2 app to use the Dancer2::Plugin::Database to give
1162       you enough skills to go out and explore other plugins on your own.
1163
1164   Installing plugins
1165       Like Dancer2 itself, Dancer2 plugins can be found on the CPAN. Use your
1166       favorite method for downloading and installing the
1167       Dancer2::Plugin::Database module on your machine. We recommend using
1168       "cpanminus" like so:
1169
1170           cpanm Dancer2::Plugin::Database
1171
1172   Using plugins
1173       Using a plugin couldn't be easier. Simply add the following line to
1174       your Dancr2 module below the "use Dancer2;" line in your module:
1175
1176           use Dancer2::Plugin::Database;
1177
1178   Configuring plugins
1179       Plugins can be configured with the YAML configuration files mentioned
1180       in Part II of this tutorial. Let's edit the "development.yml" file and
1181       add our database configuration there. Below the last line in that file,
1182       add the following lines, being careful to keep the indentation as you
1183       see it here:
1184
1185         plugins:                 ; all plugin configuration settings go in this section
1186           Database:              ; the name of our plugin
1187             driver: "SQLite"     ; driver we want to use
1188             database: "dancr.db" ; where the database will go in our app
1189                                  ; run a query when connecting to the datbase:
1190             on_connect_do: [ "create table if not exists entries (id integer primary key autoincrement, title string not null, text string not null)" ]
1191
1192       Here, we direct our database plugin to use the "SQLite" driver and to
1193       place the database in the root directory of our Dancr2. The
1194       "on_connect_db" setting tells the plugin to run an SQL query when it
1195       connects with the database to create a table for us if it doesn't
1196       already exist.
1197
1198   Modifying our database code in the Dancr2 module
1199       Now it's time to modify our Dancr2 module so it will use the plugin to
1200       query the database instead of our own code. There are a few things to
1201       do. First, we will delete the code we no longer need.
1202
1203       Since our configuration file tells the plugin where our database is, we
1204       can delete this line:
1205
1206           set 'database' => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
1207
1208       And since the database plugin will create our database connection and
1209       initialize our database for us, we can scrap the following two
1210       subroutines and line from our module:
1211
1212           sub connect_db {
1213               my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database'))
1214                   or die $DBI::errstr;
1215
1216               return $dbh;
1217           }
1218
1219           sub init_db {
1220               my $db = connect_db();
1221               my $schema = read_text('./schema.sql');
1222               $db->do($schema)
1223                   or die $db->errstr;
1224           }
1225
1226           init_db(); # Found at the bottom of our file
1227
1228       With that done, let's now take advantage of a hook the plugin provides
1229       us that we can use to handle certain events by adding the following
1230       command to our module to handle database errors:
1231
1232           hook 'database_error' => sub {
1233               my $error = shift;
1234               die $error;
1235           };
1236
1237       Now let's make a few adjustments to the bits of code that make the
1238       database queries. In our "get '/'" route, change all instances of $db
1239       with "database" and remove all the "die" calls since we now have a hook
1240       to handle the errors for us. When you are done, your route should look
1241       something like this:
1242
1243           get '/' => sub {
1244               my $sql = 'select id, title, text from entries order by id desc';
1245               my $sth = database->prepare($sql);
1246               $sth->execute;
1247               template 'show_entries.tt', {
1248                   msg           => get_flash(),
1249                   add_entry_url => uri_for('/add'),
1250                   entries       => $sth->fetchall_hashref('id'),
1251               };
1252           };
1253
1254       Make the same changes to the "post '/add'" route to transform it into
1255       this:
1256
1257           post '/add' => sub {
1258               if ( not session('logged_in') ) {
1259                   send_error("Not logged in", 401);
1260               }
1261
1262               my $sql = 'insert into entries (title, text) values (?, ?)';
1263               my $sth = database->prepare($sql);
1264               $sth->execute(
1265                   body_parameters->get('title'),
1266                   body_parameters->get('text')
1267               );
1268
1269               set_flash('New entry posted!');
1270               redirect '/';
1271           };
1272
1273       Our last step is to get rid of the following lines which we no longer
1274       need, thanks to our plugin:
1275
1276         use DBI;
1277         use File::Spec;
1278         use File::Slurper qw/ read_text /;
1279
1280       That's it! Now start your app with "plackup" to make sure you don't get
1281       any errors and then point your browser to test the app to make sure it
1282       works as expected. If it doesn't, double and triple check your
1283       configuration settings and your module's code which should now look
1284       like this:
1285
1286           package Dancr2;
1287           use Dancer2;
1288           use Dancer2::Plugin::Database;
1289
1290           our $VERSION = '0.1';
1291
1292           my $flash;
1293
1294           sub set_flash {
1295               my $message = shift;
1296
1297               $flash = $message;
1298           }
1299
1300           sub get_flash {
1301               my $msg = $flash;
1302               $flash  = "";
1303
1304               return $msg;
1305           }
1306
1307           hook before_template_render => sub {
1308               my $tokens = shift;
1309
1310               $tokens->{'css_url'}    = request->base . 'css/style.css';
1311               $tokens->{'login_url'}  = uri_for('/login');
1312               $tokens->{'logout_url'} = uri_for('/logout');
1313           };
1314
1315           get '/' => sub {
1316               my $sql = 'select id, title, text from entries order by id desc';
1317               my $sth = database->prepare($sql);
1318               $sth->execute;
1319               template 'show_entries.tt', {
1320                   msg           => get_flash(),
1321                   add_entry_url => uri_for('/add'),
1322                   entries       => $sth->fetchall_hashref('id'),
1323               };
1324           };
1325
1326           post '/add' => sub {
1327               if ( not session('logged_in') ) {
1328                   send_error("Not logged in", 401);
1329               }
1330
1331               my $sql = 'insert into entries (title, text) values (?, ?)';
1332               my $sth = database->prepare($sql);
1333               $sth->execute(
1334                   body_parameters->get('title'),
1335                   body_parameters->get('text')
1336               );
1337
1338               set_flash('New entry posted!');
1339               redirect '/';
1340           };
1341
1342           any ['get', 'post'] => '/login' => sub {
1343               my $err;
1344
1345               if ( request->method() eq "POST" ) {
1346                   # process form input
1347                   if ( params->{'username'} ne setting('username') ) {
1348                       $err = "Invalid username";
1349                   }
1350                   elsif ( params->{'password'} ne setting('password') ) {
1351                       $err = "Invalid password";
1352                   }
1353                   else {
1354                       session 'logged_in' => true;
1355                       set_flash('You are logged in.');
1356                       return redirect '/';
1357                   }
1358               }
1359
1360               # display login form
1361               template 'login.tt', {
1362                   err => $err,
1363               };
1364
1365           };
1366
1367           get '/logout' => sub {
1368               app->destroy_session;
1369               set_flash('You are logged out.');
1370               redirect '/';
1371           };
1372
1373           true;
1374
1375   Next steps
1376       Congrats! You are now using the database plugin like a boss. The
1377       database plugin does a lot more than what we showed you here. We'll
1378       leave it up to you to consult the Dancer2::Plugin::Database to unlock
1379       its full potential.
1380
1381       There are many more plugins for you to explore. You now know enough to
1382       install and experiment with them. Some of the more popular and useful
1383       plugins are listed at Dancer2::Plugins. You can also search CPAN with
1384       "Dancer2::Plugin" for a more comprehensive listing.
1385
1386       If you are feeling really inspired, you can learn how to extend Dancer2
1387       with your own plugins by reading Dancer2::Plugin.
1388

Happy dancing!

1390       I hope these tutorials have been helpful and interesting enough to get
1391       you exploring Dancer2 on your own. The framework is still under
1392       development but it's definitely mature enough to use in a production
1393       project.
1394
1395       Happy dancing!
1396
1397   One more thing: Test!
1398       Before we go, we want to mention that Dancer2 makes it very easy to run
1399       automated tests on your app to help you find bugs. If you are new to
1400       testing, we encourage you to start learning how. Your future self will
1401       thank you.  The effort you put into creating tests for your app will
1402       save you many hours of frustration in the long run. Unfortunately,
1403       until we get Part IV of this tutorial written, you'll have to consult
1404       the Dancer2 testing documentation for more details on how to test your
1405       app.
1406
1407       Enjoy!
1408

SEE ALSO

1410       ·   <http://perldancer.org>
1411
1412       ·   <http://github.com/PerlDancer/Dancer2>
1413
1414       ·   Dancer2::Plugins
1415
1417       The CSS stylesheet is copied verbatim from the Flaskr example
1418       application and is subject to their license:
1419
1420       Copyright (c) 2010, 2013 by Armin Ronacher and contributors.
1421
1422       Some rights reserved.
1423
1424       Redistribution and use in source and binary forms of the software as
1425       well as documentation, with or without modification, are permitted
1426       provided that the following conditions are met:
1427
1428       ·   Redistributions of source code must retain the above copyright
1429           notice, this list of conditions and the following disclaimer.
1430
1431       ·   Redistributions in binary form must reproduce the above copyright
1432           notice, this list of conditions and the following disclaimer in the
1433           documentation and/or other materials provided with the
1434           distribution.
1435
1436       ·   The names of the contributors may not be used to endorse or promote
1437           products derived from this software without specific prior written
1438           permission.
1439

AUTHOR

1441       Dancer Core Developers
1442
1444       This software is copyright (c) 2020 by Alexis Sukrieh.
1445
1446       This is free software; you can redistribute it and/or modify it under
1447       the same terms as the Perl 5 programming language system itself.
1448
1449
1450
1451perl v5.32.0                      2020-07-28              Dancer2::Tutorial(3)
Impressum