1Dancer2::Cookbook(3)  User Contributed Perl Documentation Dancer2::Cookbook(3)
2
3
4

NAME

6       Dancer2::Cookbook - Example-driven quick-start to the Dancer2 web
7       framework
8

VERSION

10       version 0.207000
11

DESCRIPTION

13       A quick-start guide with examples to get you up and running with the
14       Dancer2 web framework. This document will be twice as useful if you
15       finish reading the manual (Dancer2::Manual) first, but that is not
16       required... :-)
17

BEGINNER'S DANCE

19   A simple Dancer2 web app
20       Dancer2 has been designed to be easy to work with - it's trivial to
21       write a simple web app, but still has the power to work with larger
22       projects. To start with, let's make an incredibly simple "Hello World"
23       example:
24
25           #!/usr/bin/env perl
26
27           use Dancer2;
28
29           get '/hello/:name' => sub {
30               return "Why, hello there " . route_parameters->get('name');
31           };
32
33           dance;
34
35       Yes - the above is a fully-functioning web app; running that script
36       will launch a webserver listening on the default port (3000). Now you
37       can make a request:
38
39           $ curl http://localhost:3000/hello/Bob
40           Why, hello there Bob
41
42       and it will say hello. The ":name" part is a named parameter within the
43       route specification, whose value is made available through
44       "route_parameters".
45
46       Note that you don't need to use the "strict" and "warnings" pragmas;
47       they are already loaded by Dancer2.
48
49   Default Route
50       In case you want to avoid a 404 error, or handle multiple routes in the
51       same way and you don't feel like configuring all of them, you can set
52       up a default route handler.
53
54       The default route handler will handle any request that doesn't get
55       served by any other route.
56
57       All you need to do is set up the following route as the last route:
58
59           any qr{.*} => sub {
60               status 'not_found';
61               template 'special_404', { path => request->path };
62           };
63
64       Then you can set up the template like so:
65
66           You tried to reach [% path %], but it is unavailable at the moment.
67
68           Please try again or contact us at <contact@example.com>.
69
70   Using the "auto_page" feature for automatic route creation
71       For simple "static" pages you can simply enable the "auto_page" config
72       setting; this means you don't need to declare a route handler for those
73       pages; if a request is for "/foo/bar", Dancer2 will check for a
74       matching view (e.g. "/foo/bar.tt") and render it with the default
75       layout, if found. For full details, see the documentation for the
76       auto_page setting.
77
78   Simplifying AJAX queries with the Ajax plugin
79       As an AJAX query is just an HTTP query, it's similar to a GET or POST
80       route.  You may ask yourself why you may want to use the "ajax" keyword
81       (from the Dancer2::Plugin::Ajax plugin) instead of a simple "get".
82
83       Let's say you have a path like "/user/:user" in your application. You
84       may want to be able to serve this page with a layout and HTML content.
85       But you may also want to be able to call this same url from a
86       javascript query using AJAX.
87
88       So, instead of having the following code:
89
90           get '/user/:user' => sub {
91                if ( request->is_ajax ) {
92                    # create xml, set headers to text/xml, blablabla
93                     header( 'Content-Type'  => 'text/xml' );
94                     header( 'Cache-Control' =>  'no-store, no-cache, must-revalidate' );
95                     to_xml({...})
96                } else {
97                    template users => {...}
98                }
99           };
100
101       you can have
102
103           ajax '/user/:user' => sub {
104                to_xml( {...}, RootName => undef );
105           }
106
107       and
108
109           get '/user/:user' => sub {
110               template users => {...}
111           }
112
113       Because it's an AJAX query, you know you need to return XML content, so
114       the content type of the response is set for you.
115
116       Example: Feeding graph data through AJAX
117
118       Let us assume we are building an application that uses a plotting
119       library to generate a graph and expects to get its data, which is in
120       the form of word count from an AJAX call.
121
122       For the graph, we need the url /data to return a JSON representation of
123       the word count data. Dancer in fact has a "encode_json()" function that
124       takes care of the JSON encapsulation.
125
126            get '/data' => sub {
127                open my $fh, '<', $count_file;
128
129                my %contestant;
130                while (<$fh>) {
131                    chomp;
132                    my ( $date, $who, $count ) = split '\s*,\s*';
133
134                    my $epoch = DateTime::Format::Flexible->parse_datetime($date)->epoch;
135                    my $time = 1000 * $epoch;
136                    $contestant{$who}{$time} = $count;
137                }
138
139                my @json;  # data structure that is going to be JSONified
140
141                while ( my ( $peep, $data ) = each %contestant ) {
142                    push @json, {
143                        label     => $peep,
144                        hoverable => \1,    # so that it becomes JavaScript's 'true'
145                        data => [ map  { [ $_, $data->{$_} ] }
146                                sort { $a <=> $b }
147                                keys %$data ],
148                    };
149                }
150
151                my $beginning = DateTime::Format::Flexible->parse_datetime( "2010-11-01")->epoch;
152                my $end       = DateTime::Format::Flexible->parse_datetime( "2010-12-01")->epoch;
153
154                push @json, {
155                    label => 'de par',
156                    data => [
157                        [$beginning * 1000, 0],
158                        [   DateTime->now->epoch * 1_000,
159                            50_000
160                              * (DateTime->now->epoch - $beginning)
161                              / ($end - $beginning)
162                        ]
163                      ],
164
165                };
166
167                encode_json( \@json );
168            };
169
170       For more serious AJAX interaction, there's also Dancer2::Plugin::Ajax
171       that adds an ajax route handler to the mix.
172
173       Because it's an AJAX query, you know you need to return XML content, so
174       the content type of the response is set for you.
175
176   Using the prefix feature to split your application
177       For better maintainability, you may want to separate some of your
178       application components into different packages. Let's say we have a
179       simple web app with an admin section and want to maintain this in a
180       different package:
181
182           package myapp;
183           use Dancer2;
184           use myapp::admin;
185
186           prefix undef;
187
188           get '/' => sub {...};
189
190           1;
191
192           package myapp::admin;
193           use Dancer2 appname => 'myapp';
194
195           prefix '/admin';
196
197           get '/' => sub {...};
198
199           1;
200
201       The following routes will be generated for us:
202
203           - get /
204           - get /admin/
205           - head /
206           - head /admin/
207
208       By default, a separate application is created for every package that
209       uses Dancer2. The "appname" tag is used to collect routes and hooks
210       into a single Dancer2 application. In the above example, "appname =>
211       'myapp'" adds the routes from "myapp::admin" to the routes of the app
212       "myapp".
213
214       When using multiple applications please ensure that your path
215       definitions do not overlap. For example, if using a default route as
216       described above, once a request is matched to the default route then no
217       further routes (or applications) would be reached.
218
219   Delivering custom error pages
220       At the Core
221
222       In Dancer2, creating new errors is done by creating a new
223       Dancer2::Core::Error
224
225            my $oopsie = Dancer2::Core::Error->new(
226                status  => 418,
227                message => "This is the Holidays. Tea not acceptable. We want eggnog.",
228                app     => $app,
229            )
230
231       If not given, the status code defaults to a 500, there is no need for a
232       message if we feel taciturn, and while the $app (which is a
233       Dancer2::Core::App object holding all the pieces of information related
234       to the current request) is needed if we want to take advantage of the
235       templates, we can also do without.
236
237       However, to be seen by the end user, we have to populate the
238       Dancer2::Core::Response object with the error's data. This is done via:
239
240            $oopsie->throw($response);
241
242       Or, if we want to use the response object already present in the $app
243       (which is usually the case):
244
245            $oopsie->throw;
246
247       This populates the status code of the response, sets its content, and
248       throws a halt() in the dispatch process.
249
250       What it will look like
251
252       The error object has quite a few ways to generate its content.
253
254       First, it can be explicitly given
255
256            my $oopsie = Dancer2::Core::Error->new(
257                content => '<html><body><h1>OMG</h1></body></html>',
258            );
259
260       If the $context was given, the error will check if there is a template
261       by the name of the status code (so, say you're using Template Toolkit,
262       418.tt) and will use it to generate the content, passing it the error's
263       $message, $status code and $title (which, if not specified, will be the
264       standard http error definition for the status code).
265
266       If there is no template, the error will then look for a static page (to
267       continue with our example, 418.html) in the public/ directory.
268
269       And finally, if all of that failed, the error object will fall back on
270       an internal template.
271
272       Errors in Routes
273
274       The simplest way to use errors in routes is:
275
276            get '/xmas/gift/:gift' => sub {
277                die "sorry, we're all out of ponies\n"
278                    if route_parameters->get('gift') eq 'pony';
279            };
280
281       The die will be intercepted by Dancer, converted into an error (status
282       code 500, message set to the dying words) and passed to the response.
283
284       In the cases where more control is required, "send_error()" is the way
285       to go:
286
287            get '/glass/eggnog' => sub {
288                send_error "Sorry, no eggnog here", 418;
289            };
290
291       And if total control is needed:
292
293            get '/xmas/wishlist' => sub {
294                Dancer2::Core::Error->new(
295                    response => response(),
296                    status   => 406,
297                    message  => "nothing but coal for you, I'm afraid",
298                    template => 'naughty/index',
299                )->throw unless user_was_nice();
300
301                ...;
302            };
303
304   Template Toolkit's WRAPPER directive in Dancer2
305       Dancer2 already provides a WRAPPER-like ability, which we call a
306       "layout".  The reason we don't use Template Toolkit's WRAPPER (which
307       also makes us incompatible with it) is because not all template systems
308       support it.  In fact, most don't.
309
310       However, you might want to use it, and be able to define META variables
311       and regular Template::Toolkit variables.
312
313       These few steps will get you there:
314
315       ·   Disable the layout in Dancer2
316
317           You can do this by simply commenting (or removing) the "layout"
318           configuration in the config file.
319
320       ·   Use the Template Toolkit template engine
321
322           Change the configuration of the template to Template Toolkit:
323
324               # in config.yml
325               template: "template_toolkit"
326
327       ·   Tell the Template Toolkit engine which wrapper to use
328
329               # in config.yml
330               # ...
331               engines:
332                   template:
333                       template_toolkit:
334                           WRAPPER: layouts/main.tt
335
336       Done! Everything will work fine out of the box, including variables and
337       META variables.
338
339       However, disabling the internal layout it will also disable the hooks
340       "before_layout_render" and "after_layout_render".
341
342   Customizing Template Toolkit in Dancer2
343       Please see Dancer2::Template::TemplateToolkit for more details.
344
345   Accessing configuration information from a separate script
346       You may want to access your webapp's configuration from outside your
347       webapp. You could, of course, use the YAML module of your choice and
348       load your webapps's "config.yml", but chances are that this is not
349       convenient.
350
351       Use Dancer2 instead. You can simply use the values from "config.yml"
352       and some additional default values:
353
354           # bin/show_app_config.pl
355           use Dancer2;
356           printf "template: %s\n", config->{'template'}; # simple
357           printf "log: %s\n",      config->{'log'};      # undef
358
359       Note that "config->{log}" should result in an uninitialized warning on
360       a default scaffold since the environment isn't loaded and log is
361       defined in the environment and not in "config.yml". Hence "undef".
362
363       Dancer2 will load your "config.yml" configuration file along with the
364       correct environment file located in your "environments" directory.
365
366       The environment is determined by two environment variables in the
367       following order:
368
369       ·   DANCER_ENVIRONMENT
370
371       ·   PLACK_ENV
372
373       If neither of those is set, it will default to loading the development
374       environment (typically "$webapp/environment/development.yml").
375
376       If you wish to load a different environment, you need to override these
377       variables.
378
379       You can call your script with the environment changed:
380
381           $ PLACK_ENV=production perl bin/show_app_config.pl
382
383       Or you can override them directly in the script (less recommended):
384
385           BEGIN { $ENV{'DANCER_ENVIRONMENT'} = 'production' }
386           use Dancer2;
387
388           ...
389
390   Using DBIx::Class
391       DBIx::Class, also known as DBIC, is one of the many Perl ORM (Object
392       Relational Mapper). It is easy to use DBIC in Dancer2 using the
393       Dancer2::Plugin::DBIC.
394
395       An example
396
397       This example demonstrates a simple Dancer2 application that allows one
398       to search for authors or books. The application is connected to a
399       database, that contains authors, and their books. The website will have
400       one single page with a form, that allows one to query books or authors,
401       and display the results.
402
403       Creating the application
404
405           $ dancer2 -a bookstore
406
407       To use the Template Toolkit as the template engine, we specify it in
408       the configuration file:
409
410           # add in bookstore/config.yml
411           template: template_toolkit
412
413       Creating the view
414
415       We need a view to display the search form, and below, the results, if
416       any. The results will be fed by the route to the view as an arrayref of
417       results. Each result is a hashref, with a author key containing the
418       name of the author, and a books key containing an arrayref of strings :
419       the books names.
420
421           # example of a list of results
422           [ { author => 'author 1',
423               books => [ 'book 1', 'book 2' ],
424             },
425             { author => 'author 2',
426               books => [ 'book 3', 'book 4' ],
427             }
428           ]
429
430       # bookstore/views/search.tt <p> <form action="/search"> Search query:
431       <input type="text" name="query" /> </form> </p> <br>
432
433       An example of the view, displaying the search form, and the results, if
434       any:
435
436           <% IF query.length %>
437             <p>Search query was : <% query %>.</p>
438             <% IF results.size %>
439               Results:
440               <ul>
441               <% FOREACH result IN results %>
442                 <li>Author: <% result.author.replace("((?i)$query)", '<b>$1</b>') %>
443                 <ul>
444                 <% FOREACH book IN result.books %>
445                   <li><% book.replace("((?i)$query)", '<b>$1</b>') %>
446                 <% END %>
447                 </ul>
448               <% END %>
449             <% ELSE %>
450               No result
451             <% END %>
452           <% END %>
453
454       Creating a Route
455
456       A simple route, to be added in the bookstore.pm module:
457
458           # add in bookstore/lib/bookstore.pm
459           get '/search' => sub {
460               my $query   = query_parameters->get('query');
461               my @results = ();
462
463               if ( length $query ) {
464                   @results = _perform_search($query);
465               }
466
467               template search => {
468                   query   => $query,
469                   results => \@results,
470               };
471           };
472
473       Creating a database
474
475       We create a SQLite file database:
476
477           $ sqlite3 bookstore.db
478           CREATE TABLE author(
479             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
480             firstname text default '' not null,
481             lastname text not null);
482
483           CREATE TABLE book(
484             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
485             author INTEGER REFERENCES author (id),
486             title text default '' not null );
487
488       Now, to populate the database with some data, we use DBIx::Class:
489
490           # populate_database.pl
491           package My::Bookstore::Schema;
492           use base qw(DBIx::Class::Schema::Loader);
493           package main;
494           my $schema = My::Bookstore::Schema->connect('dbi:SQLite:dbname=bookstore.db');
495           $schema->populate('Author', [
496             [ 'firstname', 'lastname'],
497             [ 'Ian M.',    'Banks'   ],
498             [ 'Richard',   'Matheson'],
499             [ 'Frank',     'Herbert' ],
500           ]);
501           my @books_list = (
502             [ 'Consider Phlebas',    'Banks'    ],
503             [ 'The Player of Games', 'Banks'    ],
504             [ 'Use of Weapons',      'Banks'    ],
505             [ 'Dune',                'Herbert'  ],
506             [ 'Dune Messiah',        'Herbert'  ],
507             [ 'Children of Dune',    'Herbert'  ],
508             [ 'The Night Stalker',   'Matheson' ],
509             [ 'The Night Strangler', 'Matheson' ],
510           );
511           # transform author names into ids
512           $_->[1] = $schema->resultset('Author')->find({ lastname => $_->[1] })->id
513             foreach (@books_list);
514           $schema->populate('Book', [
515             [ 'title', 'author' ],
516             @books_list,
517           ]);
518
519       Then run it in the directory where bookstore.db sits:
520
521           perl populate_database.db
522
523       Using Dancer2::Plugin::DBIC
524
525       There are 2 ways of configuring DBIC to understand how the data is
526       organized in your database:
527
528       ·   Use auto-detection
529
530           The configuration file needs to be updated to indicate the use of
531           the Dancer2::Plugin::DBIC plugin, define a new DBIC schema called
532           bookstore and to indicate that this schema is connected to the
533           SQLite database we created.
534
535               # add in bookstore/config.yml
536               plugins:
537                 DBIC:
538                   bookstore:
539                     dsn:  "dbi:SQLite:dbname=bookstore.db"
540
541           Now, "_perform_search" can be implemented using
542           Dancer2::Plugin::DBIC. The plugin gives you access to an additional
543           keyword called schema, which you give the name of schema you want
544           to retrieve. It returns a "DBIx::Class::Schema::Loader" which can
545           be used to get a resultset and perform searches, as per standard
546           usage of DBIX::Class.
547
548               # add in bookstore/lib/bookstore.pm
549               sub _perform_search {
550                   my ($query) = @_;
551                   my $bookstore_schema = schema 'bookstore';
552                   my @results;
553                   # search in authors
554                   my @authors = $bookstore_schema->resultset('Author')->search({
555                     -or => [
556                       firstname => { like => "%$query%" },
557                       lastname  => { like => "%$query%" },
558                     ]
559                   });
560                   push @results, map {
561                       { author => join(' ', $_->firstname, $_->lastname),
562                         books => [],
563                       }
564                   } @authors;
565                   my %book_results;
566                   # search in books
567                   my @books = $bookstore_schema->resultset('Book')->search({
568                       title => { like => "%$query%" },
569                   });
570                   foreach my $book (@books) {
571                       my $author_name = join(' ', $book->author->firstname, $book->author->lastname);
572                       push @{$book_results{$author_name}}, $book->title;
573                   }
574                   push @results, map {
575                       { author => $_,
576                         books => $book_results{$_},
577                       }
578                   } keys %book_results;
579                   return @results;
580               }
581
582       ·   Use home made schema classes
583
584           The DBIx::Class::MooseColumns lets you write the DBIC schema
585           classes using Moose. The schema classes should be put in a place
586           that Dancer2 will find. A good place is in bookstore/lib/.
587
588           Once your schema classes are in place, all you need to do is modify
589           config.yml to specify that you want to use them, instead of the
590           default auto-detection method:
591
592               # change in bookstore/config.yml
593               plugins:
594                 DBIC:
595                   bookstore:
596                     schema_class: My::Bookstore::Schema
597                     dsn: "dbi:SQLite:dbname=bookstore.db"
598
599           Starting the application: Our bookstore lookup application can now
600           be started using the built-in server:
601
602               # start the web application
603               plackup bin/app.psgi
604
605   Authentication
606       Writing a form for authentication is simple: we check the user
607       credentials on a request and decide whether to continue or redirect
608       them to a form.  The form allows them to submit their username and
609       password and we save that and create a session for them so when they
610       now try the original request, we recognize them and allow them in.
611
612       Basic Application
613
614       The application is fairly simple. We have a route that needs
615       authentication, we have a route for showing the login page, and we have
616       a route for posting login information and creating a session.
617
618            package MyApp;
619            use Dancer2;
620
621            get '/' => sub {
622                session('user')
623                    or redirect('/login');
624
625                template index => {};
626            };
627
628            get '/login' => sub {
629                template login => {};
630            };
631
632            post '/login' => sub {
633                my $username  = query_parameters->get('username');
634                my $password  = query_parameters->get('password');
635                my $redir_url = query_parameters->get('redirect_url') || '/login';
636
637                $username eq 'john' && $password eq 'correcthorsebatterystaple'
638                    or redirect $redir_url;
639
640                session user => $username;
641                redirect $redir_url;
642            };
643
644       Tiny Authentication Helper
645
646       Dancer2::Plugin::Auth::Tiny allows you to abstract away not only the
647       part that checks whether the session exists, but to also generate a
648       redirect with the right path and return URL.
649
650       We simply have to define what routes needs a login using Auth::Tiny's
651       "needs" keyword.
652
653            get '/' => needs login => sub {
654                template index => {};
655            };
656
657       It creates a proper return URL using "uri_for" and the address from
658       which the user arrived.
659
660       We can thus decorate all of our private routes to require
661       authentication in this manner. If a user does not have a session, it
662       will automatically forward it to /login, in which we would render a
663       form for the user to send a login request.
664
665       Auth::Tiny even provides a new parameter, "return_url", which can be
666       used to send the user back to their original requested path.
667
668       Password Hashing
669
670       Dancer2::Plugin::Passphrase provides a simple passwords-as-objects
671       interface with sane defaults for hashed passwords which you can use in
672       your web application. It uses bcrypt as the default but supports
673       anything the Digest interface does.
674
675       Assuming we have the original user-creation form submitting a username
676       and password:
677
678            package MyApp;
679            use Dancer2;
680            use Dancer2::Plugin::Passphrase;
681            post '/register' => sub {
682                my $username = query_parameters->get('username');
683                my $password = passphrase(
684                    query_parameters->get('password')
685                )->generate;
686
687                # $password is now a hashed password object
688                save_user_in_db( $username, $password->rfc2307 );
689
690                template registered => { success => 1 };
691            };
692
693       We can now add the POST method for verifying that username and
694       password:
695
696            post '/login' => sub {
697                my $username   = query_parameters->get('username');
698                my $password   = query_parameters->get('password');
699                my $saved_pass = fetch_password_from_db($username);
700
701                if ( passphrase($password)->matches($saved_pass) ) {
702                    session user => $username;
703                    redirect query_parameters->get('return_url') || '/';
704                }
705
706                # let's render instead of redirect...
707                template login => { error => 'Invalid username or password' };
708            };
709
710   Writing a REST application
711       With Dancer2, it's easy to write REST applications. Dancer2 provides
712       helpers to serialize and deserialize for the following data formats:
713
714       JSON
715       YAML
716       XML
717       Data::Dumper
718
719       To activate this feature, you only have to set the "serializer" setting
720       to the format you require, for instance in your config file:
721
722          serializer: JSON
723
724       Or directly in your code:
725
726          set serializer => 'JSON';
727
728       From now, all hashrefs or arrayrefs returned by a route will be
729       serialized to the format you chose, and all data received from POST or
730       PUT requests will be automatically deserialized.
731
732           get '/hello/:name' => sub {
733               # this structure will be returned to the client as
734               # {"name":"$name"}
735               return { name => query_parameters->get('name') };
736           };
737
738       It's possible to let the client choose which serializer to use. For
739       this, use the "mutable" serializer, and an appropriate serializer will
740       be chosen from the "Content-Type" header.
741
742       It's also possible to return a custom error using the send_error
743       keyword. When you don't use a serializer, the "send_error" function
744       will take a string as first parameter (the message), and an optional
745       HTTP code. When using a serializer, the message can be a string, an
746       arrayref or a hashref:
747
748           get '/hello/:name' => sub {
749               if (...) {
750                  send_error("you can't do that");
751                  # or
752                  send_error({reason => 'access denied', message => "no"});
753               }
754           };
755
756       The content of the error will be serialized using the appropriate
757       serializer.
758
759   Using the serializer
760       Serializers essentially do two things:
761
762       ·   Deserialize incoming requests
763
764           When a user makes a request with serialized input, the serializer
765           automatically deserializes it into actual input parameters.
766
767       ·   Serialize outgoing responses
768
769           When you return a data structure from a route, it will
770           automatically serialize it for you before returning it to the user.
771
772       Configuring
773
774       In order to configure a serializer, you just need to pick which format
775       you want for encoding/decoding (e.g. JSON) and set it up using the
776       "serializer" configuration keyword.
777
778       It is recommended to explicitly add it in the actual code instead of
779       the configuration file so it doesn't apply automatically to every app
780       that reads the configuration file (unless that's what you want):
781
782            package MyApp;
783            use Dancer2;
784            set serializer => 'JSON'; # Dancer2::Serializer::JSON
785
786            ...
787
788       Using
789
790       Now that we have a serializer set up, we can just return data
791       structures:
792
793            get '/' => sub {
794                return { resources => \%resources };
795            };
796
797       When we return this data structure, it will automatically be serialized
798       into JSON. No other code is necessary.
799
800       We also now receive requests in JSON:
801
802            post '/:entity/:id' => sub {
803                my $entity = route_parameters->get('entity');
804                my $id     = route_parameters->get('id');
805
806                # input which was sent serialized
807                my $user = body_parameters->get('user');
808
809                ...
810            };
811
812       We can now make a serialized request:
813
814            $ curl -X POST http://ourdomain/person/16 -d '{"user":"sawyer_x"}'
815
816       App-specific feature
817
818       Serializers are engines. They affect a Dancer Application, which means
819       that once you've set a serializer, all routes within that package will
820       be serialized and deserialized. This is how the feature works.
821
822       As suggested above, if you would like to have both, you need to create
823       another application which will not be serialized.
824
825       A common usage for this is an API providing serialized endpoints (and
826       receiving serialized requests) and providing rendered pages.
827
828            # MyApp.pm
829            package MyApp;
830            use Dancer2;
831
832            # another useful feature:
833            set auto_page => 1;
834
835            get '/' => sub { template 'index' => {...} };
836
837            # MyApp/API.pm
838            package MyApp::API;
839            use Dancer2;
840            set serializer => 'JSON'; # or any other serializer
841
842            get '/' => sub { +{ resources => \%resources, ... } };
843
844            # user-specific routes, for example
845            prefix '/users' => sub {
846                get '/view'     => sub {...};
847                get '/view/:id' => sub {...};
848                put '/add'      => sub {...}; # automatically deserialized params
849            };
850
851            ...
852
853       Then those will be mounted together for a single app:
854
855            # handler: app.pl:
856            use MyApp;
857            use MyApp::API;
858            use Plack::Builder;
859
860            builder {
861                mount '/'    => MyApp->to_app;
862                mount '/api' => MyApp::API->to_app;
863            };
864
865       If you want use redirect from a mounted package to the application's
866       root URI, Dancer2::Plugin::RootURIFor makes this possible:
867
868           package OurWiki;
869           use Dancer;
870           use Dancer2::Plugin::RootURIFor;
871
872           get '/:some_path' => sub {
873               redirect root_uri_for('/');
874           }
875
876       An example: Writing API interfaces
877
878       This example demonstrates an app that makes a request to a weather API
879       and then displays it dynamically in a web page.
880
881       Other than Dancer2 for defining routes, we will use HTTP::Tiny to make
882       the weather API request, JSON to decode it from JSON format, and
883       finally File::Spec to provide a fully-qualified path to our template
884       engine.
885
886            use JSON;
887            use Dancer2;
888            use HTTP::Tiny;
889            use File::Spec;
890
891       Configuration
892
893       We use the Template::Toolkit template system for this app.  Dancer
894       searches for our templates in our views directory, which defaults to
895       views directory in our current directory. Since we want to put our
896       template in our current directory, we will configure that. However,
897       Template::Toolkit does not want us to provide a relative path without
898       configuring it to allow it. This is a security issue. So, we're using
899       File::Spec to create a full path to where we are.
900
901       We also unset the default layout, so Dancer won't try to wrap our
902       template with another one. This is a feature in Dancer to allow you to
903       wrap your templates with a layout when your templating system doesn't
904       support it. Since we're not using a layout here, we don't need it.
905
906            set template => 'template_toolkit';       # set template engine
907            set layout   => undef;                    # disable layout
908            set views    => File::Spec->rel2abs('.'); # full path to views
909
910       Now, we define our URL:
911
912            my $url = 'http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial';
913
914       Route
915
916       We will define a main route which, upon a request, will fetch the
917       information from the weather API, decode it, and then display it to the
918       user.
919
920       Route definition:
921
922            get '/' => sub {
923                ...
924            };
925
926       Editing the stub of route dispatching code, we start by making the
927       request and decoding it:
928
929            # fetch data
930            my $res = HTTP::Tiny->new->get($url);
931
932            # decode request
933            my $data = decode_json $res->{'content'};
934
935       The data is not just a flat hash. It's a deep structure. In this
936       example, we will filter it for only the simple keys in the retrieved
937       data:
938
939            my $metrics = { map +(
940                ref $data->{$_} ? () : ( $_ => $data->{$_} )
941            ), keys %{$data} };
942
943       All that is left now is to render it:
944
945            template index => { metrics => $metrics };
946

NON-STANDARD STEPS

948   Turning off warnings
949       The "warnings" pragma is already used when one loads Dancer2. However,
950       if you really do not want the "warnings" pragma (for example, due to an
951       undesired warning about use of undef values), add a "no warnings"
952       pragma to the appropriate block in your module or psgi file.
953

AUTHOR

955       Dancer Core Developers
956
958       This software is copyright (c) 2018 by Alexis Sukrieh.
959
960       This is free software; you can redistribute it and/or modify it under
961       the same terms as the Perl 5 programming language system itself.
962
963
964
965perl v5.28.1                      2018-11-14              Dancer2::Cookbook(3)
Impressum