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