1Dancer2::Cookbook(3) User Contributed Perl Documentation Dancer2::Cookbook(3)
2
3
4
6 Dancer2::Cookbook - Example-driven quick-start to the Dancer2 web
7 framework
8
10 version 0.207000
11
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
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
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
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.0 2018-11-14 Dancer2::Cookbook(3)