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.300005
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
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
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
961 Dancer Core Developers
962
964 This software is copyright (c) 2021 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.32.1 2021-01-31 Dancer2::Cookbook(3)