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.300000
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
431           # bookstore/views/search.tt
432           <p>
433           <form action="/search">
434           Search query: <input type="text" name="query" />
435           </form>
436           </p>
437           <br>
438
439       An example of the view, displaying the search form, and the results, if
440       any:
441
442           <% IF query.length %>
443             <p>Search query was : <% query %>.</p>
444             <% IF results.size %>
445               Results:
446               <ul>
447               <% FOREACH result IN results %>
448                 <li>Author: <% result.author.replace("((?i)$query)", '<b>$1</b>') %>
449                 <ul>
450                 <% FOREACH book IN result.books %>
451                   <li><% book.replace("((?i)$query)", '<b>$1</b>') %>
452                 <% END %>
453                 </ul>
454               <% END %>
455             <% ELSE %>
456               No result
457             <% END %>
458           <% END %>
459
460       Creating a Route
461
462       A simple route, to be added in the bookstore.pm module:
463
464           # add in bookstore/lib/bookstore.pm
465           get '/search' => sub {
466               my $query   = query_parameters->get('query');
467               my @results = ();
468
469               if ( length $query ) {
470                   @results = _perform_search($query);
471               }
472
473               template search => {
474                   query   => $query,
475                   results => \@results,
476               };
477           };
478
479       Creating a database
480
481       We create a SQLite file database:
482
483           $ sqlite3 bookstore.db
484           CREATE TABLE author(
485             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
486             firstname text default '' not null,
487             lastname text not null);
488
489           CREATE TABLE book(
490             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
491             author INTEGER REFERENCES author (id),
492             title text default '' not null );
493
494       Now, to populate the database with some data, we use DBIx::Class:
495
496           # populate_database.pl
497           package My::Bookstore::Schema;
498           use base qw(DBIx::Class::Schema::Loader);
499           package main;
500           my $schema = My::Bookstore::Schema->connect('dbi:SQLite:dbname=bookstore.db');
501           $schema->populate('Author', [
502             [ 'firstname', 'lastname'],
503             [ 'Ian M.',    'Banks'   ],
504             [ 'Richard',   'Matheson'],
505             [ 'Frank',     'Herbert' ],
506           ]);
507           my @books_list = (
508             [ 'Consider Phlebas',    'Banks'    ],
509             [ 'The Player of Games', 'Banks'    ],
510             [ 'Use of Weapons',      'Banks'    ],
511             [ 'Dune',                'Herbert'  ],
512             [ 'Dune Messiah',        'Herbert'  ],
513             [ 'Children of Dune',    'Herbert'  ],
514             [ 'The Night Stalker',   'Matheson' ],
515             [ 'The Night Strangler', 'Matheson' ],
516           );
517           # transform author names into ids
518           $_->[1] = $schema->resultset('Author')->find({ lastname => $_->[1] })->id
519             foreach (@books_list);
520           $schema->populate('Book', [
521             [ 'title', 'author' ],
522             @books_list,
523           ]);
524
525       Then run it in the directory where bookstore.db sits:
526
527           perl populate_database.db
528
529       Using Dancer2::Plugin::DBIC
530
531       There are 2 ways of configuring DBIC to understand how the data is
532       organized in your database:
533
534       ·   Use auto-detection
535
536           The configuration file needs to be updated to indicate the use of
537           the Dancer2::Plugin::DBIC plugin, define a new DBIC schema called
538           bookstore and to indicate that this schema is connected to the
539           SQLite database we created.
540
541               # add in bookstore/config.yml
542               plugins:
543                 DBIC:
544                   bookstore:
545                     dsn:  "dbi:SQLite:dbname=bookstore.db"
546
547           Now, "_perform_search" can be implemented using
548           Dancer2::Plugin::DBIC. The plugin gives you access to an additional
549           keyword called schema, which you give the name of schema you want
550           to retrieve. It returns a "DBIx::Class::Schema::Loader" which can
551           be used to get a resultset and perform searches, as per standard
552           usage of DBIX::Class.
553
554               # add in bookstore/lib/bookstore.pm
555               sub _perform_search {
556                   my ($query) = @_;
557                   my $bookstore_schema = schema 'bookstore';
558                   my @results;
559                   # search in authors
560                   my @authors = $bookstore_schema->resultset('Author')->search({
561                     -or => [
562                       firstname => { like => "%$query%" },
563                       lastname  => { like => "%$query%" },
564                     ]
565                   });
566                   push @results, map {
567                       { author => join(' ', $_->firstname, $_->lastname),
568                         books => [],
569                       }
570                   } @authors;
571                   my %book_results;
572                   # search in books
573                   my @books = $bookstore_schema->resultset('Book')->search({
574                       title => { like => "%$query%" },
575                   });
576                   foreach my $book (@books) {
577                       my $author_name = join(' ', $book->author->firstname, $book->author->lastname);
578                       push @{$book_results{$author_name}}, $book->title;
579                   }
580                   push @results, map {
581                       { author => $_,
582                         books => $book_results{$_},
583                       }
584                   } keys %book_results;
585                   return @results;
586               }
587
588       ·   Use home made schema classes
589
590           The DBIx::Class::MooseColumns lets you write the DBIC schema
591           classes using Moose. The schema classes should be put in a place
592           that Dancer2 will find. A good place is in bookstore/lib/.
593
594           Once your schema classes are in place, all you need to do is modify
595           config.yml to specify that you want to use them, instead of the
596           default auto-detection method:
597
598               # change in bookstore/config.yml
599               plugins:
600                 DBIC:
601                   bookstore:
602                     schema_class: My::Bookstore::Schema
603                     dsn: "dbi:SQLite:dbname=bookstore.db"
604
605           Starting the application: Our bookstore lookup application can now
606           be started using the built-in server:
607
608               # start the web application
609               plackup bin/app.psgi
610
611   Authentication
612       Writing a form for authentication is simple: we check the user
613       credentials on a request and decide whether to continue or redirect
614       them to a form.  The form allows them to submit their username and
615       password and we save that and create a session for them so when they
616       now try the original request, we recognize them and allow them in.
617
618       Basic Application
619
620       The application is fairly simple. We have a route that needs
621       authentication, we have a route for showing the login page, and we have
622       a route for posting login information and creating a session.
623
624            package MyApp;
625            use Dancer2;
626
627            get '/' => sub {
628                session('user')
629                    or redirect('/login');
630
631                template index => {};
632            };
633
634            get '/login' => sub {
635                template login => {};
636            };
637
638            post '/login' => sub {
639                my $username  = query_parameters->get('username');
640                my $password  = query_parameters->get('password');
641                my $redir_url = query_parameters->get('redirect_url') || '/login';
642
643                $username eq 'john' && $password eq 'correcthorsebatterystaple'
644                    or redirect $redir_url;
645
646                session user => $username;
647                redirect $redir_url;
648            };
649
650       Tiny Authentication Helper
651
652       Dancer2::Plugin::Auth::Tiny allows you to abstract away not only the
653       part that checks whether the session exists, but to also generate a
654       redirect with the right path and return URL.
655
656       We simply have to define what routes needs a login using Auth::Tiny's
657       "needs" keyword.
658
659            get '/' => needs login => sub {
660                template index => {};
661            };
662
663       It creates a proper return URL using "uri_for" and the address from
664       which the user arrived.
665
666       We can thus decorate all of our private routes to require
667       authentication in this manner. If a user does not have a session, it
668       will automatically forward it to /login, in which we would render a
669       form for the user to send a login request.
670
671       Auth::Tiny even provides a new parameter, "return_url", which can be
672       used to send the user back to their original requested path.
673
674       Password Hashing
675
676       Dancer2::Plugin::Passphrase provides a simple passwords-as-objects
677       interface with sane defaults for hashed passwords which you can use in
678       your web application. It uses bcrypt as the default but supports
679       anything the Digest interface does.
680
681       Assuming we have the original user-creation form submitting a username
682       and password:
683
684            package MyApp;
685            use Dancer2;
686            use Dancer2::Plugin::Passphrase;
687            post '/register' => sub {
688                my $username = query_parameters->get('username');
689                my $password = passphrase(
690                    query_parameters->get('password')
691                )->generate;
692
693                # $password is now a hashed password object
694                save_user_in_db( $username, $password->rfc2307 );
695
696                template registered => { success => 1 };
697            };
698
699       We can now add the POST method for verifying that username and
700       password:
701
702            post '/login' => sub {
703                my $username   = query_parameters->get('username');
704                my $password   = query_parameters->get('password');
705                my $saved_pass = fetch_password_from_db($username);
706
707                if ( passphrase($password)->matches($saved_pass) ) {
708                    session user => $username;
709                    redirect query_parameters->get('return_url') || '/';
710                }
711
712                # let's render instead of redirect...
713                template login => { error => 'Invalid username or password' };
714            };
715
716   Writing a REST application
717       With Dancer2, it's easy to write REST applications. Dancer2 provides
718       helpers to serialize and deserialize for the following data formats:
719
720       JSON
721       YAML
722       XML
723       Data::Dumper
724
725       To activate this feature, you only have to set the "serializer" setting
726       to the format you require, for instance in your config file:
727
728          serializer: JSON
729
730       Or directly in your code:
731
732          set serializer => 'JSON';
733
734       From now, all hashrefs or arrayrefs returned by a route will be
735       serialized to the format you chose, and all data received from POST or
736       PUT requests will be automatically deserialized.
737
738           get '/hello/:name' => sub {
739               # this structure will be returned to the client as
740               # {"name":"$name"}
741               return { name => query_parameters->get('name') };
742           };
743
744       It's possible to let the client choose which serializer to use. For
745       this, use the "mutable" serializer, and an appropriate serializer will
746       be chosen from the "Content-Type" header.
747
748       It's also possible to return a custom error using the send_error
749       keyword. When you don't use a serializer, the "send_error" function
750       will take a string as first parameter (the message), and an optional
751       HTTP code. When using a serializer, the message can be a string, an
752       arrayref or a hashref:
753
754           get '/hello/:name' => sub {
755               if (...) {
756                  send_error("you can't do that");
757                  # or
758                  send_error({reason => 'access denied', message => "no"});
759               }
760           };
761
762       The content of the error will be serialized using the appropriate
763       serializer.
764
765   Using the serializer
766       Serializers essentially do two things:
767
768       ·   Deserialize incoming requests
769
770           When a user makes a request with serialized input, the serializer
771           automatically deserializes it into actual input parameters.
772
773       ·   Serialize outgoing responses
774
775           When you return a data structure from a route, it will
776           automatically serialize it for you before returning it to the user.
777
778       Configuring
779
780       In order to configure a serializer, you just need to pick which format
781       you want for encoding/decoding (e.g. JSON) and set it up using the
782       "serializer" configuration keyword.
783
784       It is recommended to explicitly add it in the actual code instead of
785       the configuration file so it doesn't apply automatically to every app
786       that reads the configuration file (unless that's what you want):
787
788            package MyApp;
789            use Dancer2;
790            set serializer => 'JSON'; # Dancer2::Serializer::JSON
791
792            ...
793
794       Using
795
796       Now that we have a serializer set up, we can just return data
797       structures:
798
799            get '/' => sub {
800                return { resources => \%resources };
801            };
802
803       When we return this data structure, it will automatically be serialized
804       into JSON. No other code is necessary.
805
806       We also now receive requests in JSON:
807
808            post '/:entity/:id' => sub {
809                my $entity = route_parameters->get('entity');
810                my $id     = route_parameters->get('id');
811
812                # input which was sent serialized
813                my $user = body_parameters->get('user');
814
815                ...
816            };
817
818       We can now make a serialized request:
819
820            $ curl -X POST http://ourdomain/person/16 -d '{"user":"sawyer_x"}'
821
822       App-specific feature
823
824       Serializers are engines. They affect a Dancer Application, which means
825       that once you've set a serializer, all routes within that package will
826       be serialized and deserialized. This is how the feature works.
827
828       As suggested above, if you would like to have both, you need to create
829       another application which will not be serialized.
830
831       A common usage for this is an API providing serialized endpoints (and
832       receiving serialized requests) and providing rendered pages.
833
834            # MyApp.pm
835            package MyApp;
836            use Dancer2;
837
838            # another useful feature:
839            set auto_page => 1;
840
841            get '/' => sub { template 'index' => {...} };
842
843            # MyApp/API.pm
844            package MyApp::API;
845            use Dancer2;
846            set serializer => 'JSON'; # or any other serializer
847
848            get '/' => sub { +{ resources => \%resources, ... } };
849
850            # user-specific routes, for example
851            prefix '/users' => sub {
852                get '/view'     => sub {...};
853                get '/view/:id' => sub {...};
854                put '/add'      => sub {...}; # automatically deserialized params
855            };
856
857            ...
858
859       Then those will be mounted together for a single app:
860
861            # handler: app.pl:
862            use MyApp;
863            use MyApp::API;
864            use Plack::Builder;
865
866            builder {
867                mount '/'    => MyApp->to_app;
868                mount '/api' => MyApp::API->to_app;
869            };
870
871       If you want use redirect from a mounted package to the application's
872       root URI, Dancer2::Plugin::RootURIFor makes this possible:
873
874           package OurWiki;
875           use Dancer;
876           use Dancer2::Plugin::RootURIFor;
877
878           get '/:some_path' => sub {
879               redirect root_uri_for('/');
880           }
881
882       An example: Writing API interfaces
883
884       This example demonstrates an app that makes a request to a weather API
885       and then displays it dynamically in a web page.
886
887       Other than Dancer2 for defining routes, we will use HTTP::Tiny to make
888       the weather API request, JSON to decode it from JSON format, and
889       finally File::Spec to provide a fully-qualified path to our template
890       engine.
891
892            use JSON;
893            use Dancer2;
894            use HTTP::Tiny;
895            use File::Spec;
896
897       Configuration
898
899       We use the Template::Toolkit template system for this app.  Dancer
900       searches for our templates in our views directory, which defaults to
901       views directory in our current directory. Since we want to put our
902       template in our current directory, we will configure that. However,
903       Template::Toolkit does not want us to provide a relative path without
904       configuring it to allow it. This is a security issue. So, we're using
905       File::Spec to create a full path to where we are.
906
907       We also unset the default layout, so Dancer won't try to wrap our
908       template with another one. This is a feature in Dancer to allow you to
909       wrap your templates with a layout when your templating system doesn't
910       support it. Since we're not using a layout here, we don't need it.
911
912            set template => 'template_toolkit';       # set template engine
913            set layout   => undef;                    # disable layout
914            set views    => File::Spec->rel2abs('.'); # full path to views
915
916       Now, we define our URL:
917
918            my $url = 'http://api.openweathermap.org/data/2.5/weather?id=5110629&units=imperial';
919
920       Route
921
922       We will define a main route which, upon a request, will fetch the
923       information from the weather API, decode it, and then display it to the
924       user.
925
926       Route definition:
927
928            get '/' => sub {
929                ...
930            };
931
932       Editing the stub of route dispatching code, we start by making the
933       request and decoding it:
934
935            # fetch data
936            my $res = HTTP::Tiny->new->get($url);
937
938            # decode request
939            my $data = decode_json $res->{'content'};
940
941       The data is not just a flat hash. It's a deep structure. In this
942       example, we will filter it for only the simple keys in the retrieved
943       data:
944
945            my $metrics = { map +(
946                ref $data->{$_} ? () : ( $_ => $data->{$_} )
947            ), keys %{$data} };
948
949       All that is left now is to render it:
950
951            template index => { metrics => $metrics };
952

NON-STANDARD STEPS

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

AUTHOR

961       Dancer Core Developers
962
964       This software is copyright (c) 2019 by Alexis Sukrieh.
965
966       This is free software; you can redistribute it and/or modify it under
967       the same terms as the Perl 5 programming language system itself.
968
969
970
971perl v5.30.1                      2020-01-29              Dancer2::Cookbook(3)
Impressum