1Mojolicious::Guides::GrUosweirngC(o3n)tributed Perl DocuMmoejnotlaitciioonus::Guides::Growing(3)
2
3
4
6 Mojolicious::Guides::Growing - Growing Mojolicious applications
7
9 This document explains the process of starting a Mojolicious::Lite
10 prototype from scratch and growing it into a well-structured
11 Mojolicious application.
12
14 Essentials every Mojolicious developer should know.
15
16 Model View Controller
17 MVC is a software architectural pattern for graphical user interface
18 programming originating in Smalltalk-80, that separates application
19 logic, presentation and input.
20
21 +------------+ +-------+ +------+
22 Input -> | Controller | -> | Model | -> | View | -> Output
23 +------------+ +-------+ +------+
24
25 A slightly modified version of the pattern moving some application
26 logic into the controller is the foundation of pretty much every web
27 framework these days, including Mojolicious.
28
29 +----------------+ +-------+
30 Request -> | | <-> | Model |
31 | | +-------+
32 | Controller |
33 | | +-------+
34 Response <- | | <-> | View |
35 +----------------+ +-------+
36
37 The controller receives a request from a user, passes incoming data to
38 the model and retrieves data from it, which then gets turned into an
39 actual response by the view. But note that this pattern is just a
40 guideline that most of the time results in cleaner more maintainable
41 code, not a rule that should be followed at all costs.
42
43 REpresentational State Transfer
44 REST is a software architectural style for distributed hypermedia
45 systems such as the web. While it can be applied to many protocols it
46 is most commonly used with HTTP these days. In REST terms, when you are
47 opening a URL like "http://mojolicious.org/foo" with your browser, you
48 are basically asking the web server for the HTML representation of the
49 "http://mojolicious.org/foo" resource.
50
51 +--------+ +--------+
52 | | -> http://mojolicious.org/foo -> | |
53 | Client | | Server |
54 | | <- <html>Mojo rocks!</html> <- | |
55 +--------+ +--------+
56
57 The fundamental idea here is that all resources are uniquely
58 addressable with URLs and every resource can have different
59 representations such as HTML, RSS or JSON. User interface concerns are
60 separated from data storage concerns and all session state is kept
61 client-side.
62
63 +---------+ +------------+
64 | | -> PUT /foo -> | |
65 | | -> Hello World! -> | |
66 | | | |
67 | | <- 201 CREATED <- | |
68 | | | |
69 | | -> GET /foo -> | |
70 | Browser | | Web Server |
71 | | <- 200 OK <- | |
72 | | <- Hello World! <- | |
73 | | | |
74 | | -> DELETE /foo -> | |
75 | | | |
76 | | <- 200 OK <- | |
77 +---------+ +------------+
78
79 While HTTP methods such as "PUT", "GET" and "DELETE" are not directly
80 part of REST they go very well with it and are commonly used to
81 manipulate resources.
82
83 Sessions
84 HTTP was designed as a stateless protocol, web servers don't know
85 anything about previous requests, which makes user-friendly login
86 systems very tricky. Sessions solve this problem by allowing web
87 applications to keep stateful information across several HTTP requests.
88
89 GET /login?user=sebastian&pass=s3cret HTTP/1.1
90 Host: mojolicious.org
91
92 HTTP/1.1 200 OK
93 Set-Cookie: sessionid=987654321
94 Content-Length: 10
95 Hello sebastian.
96
97 GET /protected HTTP/1.1
98 Host: mojolicious.org
99 Cookie: sessionid=987654321
100
101 HTTP/1.1 200 OK
102 Set-Cookie: sessionid=987654321
103 Content-Length: 16
104 Hello again sebastian.
105
106 Traditionally all session data was stored on the server-side and only
107 session ids were exchanged between browser and web server in the form
108 of cookies.
109
110 Set-Cookie: session=hmac-sha1(base64(json($session)))
111
112 In Mojolicious however we are taking this concept one step further by
113 storing everything JSON serialized and Base64 encoded in HMAC-SHA1
114 signed cookies, which is more compatible with the REST philosophy and
115 reduces infrastructure requirements.
116
117 Test-Driven Development
118 TDD is a software development process where the developer starts
119 writing failing test cases that define the desired functionality and
120 then moves on to producing code that passes these tests. There are many
121 advantages such as always having good test coverage and code being
122 designed for testability, which will in turn often prevent future
123 changes from breaking old code. Much of Mojolicious was developed using
124 TDD.
125
127 One of the main differences between Mojolicious and other web
128 frameworks is that it also includes Mojolicious::Lite, a micro web
129 framework optimized for rapid prototyping.
130
131 Differences
132 You likely know the feeling, you've got a really cool idea and want to
133 try it as quickly as possible, that's exactly why Mojolicious::Lite
134 applications don't need more than a single file.
135
136 myapp.pl # Templates and even static files can be inlined
137
138 Full Mojolicious applications on the other hand are much closer to a
139 well organized CPAN distribution to maximize maintainability.
140
141 myapp # Application directory
142 |- script # Script directory
143 | +- my_app # Application script
144 |- lib # Library directory
145 | |- MyApp.pm # Application class
146 | +- MyApp # Application namespace
147 | +- Controller # Controller namespace
148 | +- Example.pm # Controller class
149 |- my_app.yml # Configuration file
150 |- t # Test directory
151 | +- basic.t # Random test
152 |- log # Log directory
153 | +- development.log # Development mode log file
154 |- public # Static file directory (served automatically)
155 | +- index.html # Static HTML file
156 +- templates # Template directory
157 |- layouts # Template directory for layouts
158 | +- default.html.ep # Layout template
159 +- example # Template directory for "Example" controller
160 +- welcome.html.ep # Template for "welcome" action
161
162 Both application skeletons can be automatically generated with the
163 commands Mojolicious::Command::Author::generate::lite_app and
164 Mojolicious::Command::Author::generate::app.
165
166 $ mojo generate lite-app myapp.pl
167 $ mojo generate app MyApp
168
169 Feature-wise both are almost equal, the only real differences are
170 organizational, so each one can be gradually transformed into the
171 other.
172
173 Foundation
174 We start our new application with a single executable Perl script.
175
176 $ mkdir myapp
177 $ cd myapp
178 $ touch myapp.pl
179 $ chmod 744 myapp.pl
180
181 This will be the foundation for our login manager example application.
182
183 #!/usr/bin/env perl
184 use Mojolicious::Lite;
185
186 get '/' => sub {
187 my $c = shift;
188 $c->render(text => 'Hello World!');
189 };
190
191 app->start;
192
193 The built-in development web server makes working on your application a
194 lot of fun thanks to automatic reloading.
195
196 $ morbo ./myapp.pl
197 Web application available at http://127.0.0.1:3000
198
199 Just save your changes and they will be automatically in effect the
200 next time you refresh your browser.
201
202 A bird's-eye view
203 It all starts with an HTTP request like this, sent by your browser.
204
205 GET / HTTP/1.1
206 Host: localhost:3000
207
208 Once the request has been received by the web server through the event
209 loop, it will be passed on to Mojolicious, where it will be handled in
210 a few simple steps.
211
212 1.
213 Check if a static file exists that would meet the requirements.
214
215 2.
216 Try to find a route that would meet the requirements.
217
218 3.
219 Dispatch the request to this route, usually reaching one or more
220 actions.
221
222 4.
223 Process the request, maybe generating a response with the renderer.
224
225 5.
226 Return control to the web server, and if no response has been
227 generated yet, wait for a non-blocking operation to do so through the
228 event loop.
229
230 With our application the router would have found an action in step 2,
231 and rendered some text in step 4, resulting in an HTTP response like
232 this being sent back to the browser.
233
234 HTTP/1.1 200 OK
235 Content-Length: 12
236 Hello World!
237
238 Model
239 In Mojolicious we consider web applications simple frontends for
240 existing business logic, that means Mojolicious is by design entirely
241 model layer agnostic and you just use whatever Perl modules you like
242 most.
243
244 $ mkdir -p lib/MyApp/Model
245 $ touch lib/MyApp/Model/Users.pm
246 $ chmod 644 lib/MyApp/Model/Users.pm
247
248 Our login manager will simply use a plain old Perl module abstracting
249 away all logic related to matching usernames and passwords. The name
250 "MyApp::Model::Users" is an arbitrary choice, and is simply used to
251 make the separation of concerns more visible.
252
253 package MyApp::Model::Users;
254
255 use strict;
256 use warnings;
257
258 use Mojo::Util qw(secure_compare);
259
260 my $USERS = {
261 joel => 'las3rs',
262 marcus => 'lulz',
263 sebastian => 'secr3t'
264 };
265
266 sub new { bless {}, shift }
267
268 sub check {
269 my ($self, $user, $pass) = @_;
270
271 # Success
272 return 1 if $USERS->{$user} && secure_compare $USERS->{$user}, $pass;
273
274 # Fail
275 return undef;
276 }
277
278 1;
279
280 A simple helper can be registered with the function "helper" in
281 Mojolicious::Lite to make our model available to all actions and
282 templates.
283
284 #!/usr/bin/env perl
285 use Mojolicious::Lite;
286
287 use lib qw(lib);
288 use MyApp::Model::Users;
289
290 # Helper to lazy initialize and store our model object
291 helper users => sub { state $users = MyApp::Model::Users->new };
292
293 # /?user=sebastian&pass=secr3t
294 any '/' => sub {
295 my $c = shift;
296
297 # Query parameters
298 my $user = $c->param('user') || '';
299 my $pass = $c->param('pass') || '';
300
301 # Check password
302 return $c->render(text => "Welcome $user.")
303 if $c->users->check($user, $pass);
304
305 # Failed
306 $c->render(text => 'Wrong username or password.');
307 };
308
309 app->start;
310
311 The method "param" in Mojolicious::Controller is used to access query
312 parameters, "POST" parameters, file uploads and route placeholders, all
313 at once.
314
315 Testing
316 In Mojolicious we take testing very serious and try to make it a
317 pleasant experience.
318
319 $ mkdir t
320 $ touch t/login.t
321 $ chmod 644 t/login.t
322
323 Test::Mojo is a scriptable HTTP user agent designed specifically for
324 testing, with many fun state of the art features such as CSS selectors
325 based on Mojo::DOM.
326
327 use Test::More;
328 use Test::Mojo;
329
330 # Include application
331 use Mojo::File qw(curfile);
332 require(curfile->dirname->sibling('myapp.pl'));
333
334 # Allow 302 redirect responses
335 my $t = Test::Mojo->new;
336 $t->ua->max_redirects(1);
337
338 # Test if the HTML login form exists
339 $t->get_ok('/')
340 ->status_is(200)
341 ->element_exists('form input[name="user"]')
342 ->element_exists('form input[name="pass"]')
343 ->element_exists('form input[type="submit"]');
344
345 # Test login with valid credentials
346 $t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
347 ->status_is(200)
348 ->text_like('html body' => qr/Welcome sebastian/);
349
350 # Test accessing a protected page
351 $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
352
353 # Test if HTML login form shows up again after logout
354 $t->get_ok('/logout')
355 ->status_is(200)
356 ->element_exists('form input[name="user"]')
357 ->element_exists('form input[name="pass"]')
358 ->element_exists('form input[type="submit"]');
359
360 done_testing();
361
362 Your application won't pass these tests, but from now on you can use
363 them to check your progress.
364
365 $ prove -l
366 $ prove -l t/login.t
367 $ prove -l -v t/login.t
368
369 Or perform quick requests right from the command line with
370 Mojolicious::Command::get.
371
372 $ ./myapp.pl get /
373 Wrong username or password.
374
375 $ ./myapp.pl get -v '/?user=sebastian&pass=secr3t'
376 GET /?user=sebastian&pass=secr3t HTTP/1.1
377 User-Agent: Mojolicious (Perl)
378 Accept-Encoding: gzip
379 Content-Length: 0
380 Host: localhost:59472
381
382 HTTP/1.1 200 OK
383 Date: Sun, 18 Jul 2010 13:09:58 GMT
384 Server: Mojolicious (Perl)
385 Content-Length: 12
386 Content-Type: text/plain
387
388 Welcome sebastian.
389
390 State keeping
391 Sessions in Mojolicious pretty much just work out of the box once you
392 start using the method "session" in Mojolicious::Controller, there is
393 no setup required, but we suggest setting a more secure passphrase with
394 "secrets" in Mojolicious.
395
396 $app->secrets(['Mojolicious rocks']);
397
398 This passphrase is used by the HMAC-SHA1 algorithm to make signed
399 cookies tamper resistant and can be changed at any time to invalidate
400 all existing sessions.
401
402 $c->session(user => 'sebastian');
403 my $user = $c->session('user');
404
405 By default all sessions expire after one hour, for more control you can
406 use the "expiration" session value to set an expiration date in seconds
407 from now.
408
409 $c->session(expiration => 3600);
410
411 And the whole session can be deleted by using the "expires" session
412 value to set an absolute expiration date in the past.
413
414 $c->session(expires => 1);
415
416 For data that should only be visible on the next request, like a
417 confirmation message after a 302 redirect performed with "redirect_to"
418 in Mojolicious::Plugin::DefaultHelpers, you can use the flash,
419 accessible through "flash" in Mojolicious::Plugin::DefaultHelpers.
420
421 $c->flash(message => 'Everything is fine.');
422 $c->redirect_to('goodbye');
423
424 Just remember that all session data gets serialized with Mojo::JSON and
425 stored in HMAC-SHA1 signed cookies, which usually have a 4096 byte
426 (4KiB) limit, depending on browser.
427
428 Final prototype
429 A final "myapp.pl" prototype passing all of the tests above could look
430 like this.
431
432 #!/usr/bin/env perl
433 use Mojolicious::Lite;
434
435 use lib qw(lib);
436 use MyApp::Model::Users;
437
438 # Make signed cookies tamper resistant
439 app->secrets(['Mojolicious rocks']);
440
441 helper users => sub { state $users = MyApp::Model::Users->new };
442
443 # Main login action
444 any '/' => sub {
445 my $c = shift;
446
447 # Query or POST parameters
448 my $user = $c->param('user') || '';
449 my $pass = $c->param('pass') || '';
450
451 # Check password and render "index.html.ep" if necessary
452 return $c->render unless $c->users->check($user, $pass);
453
454 # Store username in session
455 $c->session(user => $user);
456
457 # Store a friendly message for the next page in flash
458 $c->flash(message => 'Thanks for logging in.');
459
460 # Redirect to protected page with a 302 response
461 $c->redirect_to('protected');
462 } => 'index';
463
464 # Make sure user is logged in for actions in this group
465 group {
466 under sub {
467 my $c = shift;
468
469 # Redirect to main page with a 302 response if user is not logged in
470 return 1 if $c->session('user');
471 $c->redirect_to('index');
472 return undef;
473 };
474
475 # A protected page auto rendering "protected.html.ep"
476 get '/protected';
477 };
478
479 # Logout action
480 get '/logout' => sub {
481 my $c = shift;
482
483 # Expire and in turn clear session automatically
484 $c->session(expires => 1);
485
486 # Redirect to main page with a 302 response
487 $c->redirect_to('index');
488 };
489
490 app->start;
491 __DATA__
492
493 @@ index.html.ep
494 % layout 'default';
495 %= form_for index => begin
496 % if (param 'user') {
497 <b>Wrong name or password, please try again.</b><br>
498 % }
499 Name:<br>
500 %= text_field 'user'
501 <br>Password:<br>
502 %= password_field 'pass'
503 <br>
504 %= submit_button 'Login'
505 % end
506
507 @@ protected.html.ep
508 % layout 'default';
509 % if (my $msg = flash 'message') {
510 <b><%= $msg %></b><br>
511 % }
512 Welcome <%= session 'user' %>.<br>
513 %= link_to Logout => 'logout'
514
515 @@ layouts/default.html.ep
516 <!DOCTYPE html>
517 <html>
518 <head><title>Login Manager</title></head>
519 <body><%= content %></body>
520 </html>
521
522 And the directory structure should be looking like this now.
523
524 myapp
525 |- myapp.pl
526 |- lib
527 | +- MyApp
528 | +- Model
529 | +- Users.pm
530 +- t
531 +- login.t
532
533 Our templates are using quite a few features of the renderer,
534 Mojolicious::Guides::Rendering explains them all in great detail.
535
537 Due to the flexibility of Mojolicious there are many variations of the
538 actual growing process, but this should give you a good overview of the
539 possibilities.
540
541 Inflating templates
542 All templates and static files inlined in the "DATA" section can be
543 automatically turned into separate files in the "templates" and
544 "public" directories with the command
545 Mojolicious::Command::Author::inflate.
546
547 $ ./myapp.pl inflate
548
549 Those directories have a higher precedence, so inflating can also be a
550 great way to allow your users to customize their applications.
551
552 Simplified application class
553 This is the heart of every full Mojolicious application and always gets
554 instantiated during server startup.
555
556 $ touch lib/MyApp.pm
557 $ chmod 644 lib/MyApp.pm
558
559 We will start by extracting all actions from "myapp.pl" and turn them
560 into simplified hybrid routes in the Mojolicious::Routes router, none
561 of the actual action code needs to be changed.
562
563 package MyApp;
564 use Mojo::Base 'Mojolicious';
565
566 use MyApp::Model::Users;
567
568 sub startup {
569 my $self = shift;
570
571 $self->secrets(['Mojolicious rocks']);
572 $self->helper(users => sub { state $users = MyApp::Model::Users->new });
573
574 my $r = $self->routes;
575
576 $r->any('/' => sub {
577 my $c = shift;
578
579 my $user = $c->param('user') || '';
580 my $pass = $c->param('pass') || '';
581 return $c->render unless $c->users->check($user, $pass);
582
583 $c->session(user => $user);
584 $c->flash(message => 'Thanks for logging in.');
585 $c->redirect_to('protected');
586 } => 'index');
587
588 my $logged_in = $r->under(sub {
589 my $c = shift;
590 return 1 if $c->session('user');
591 $c->redirect_to('index');
592 return undef;
593 });
594 $logged_in->get('/protected');
595
596 $r->get('/logout' => sub {
597 my $c = shift;
598 $c->session(expires => 1);
599 $c->redirect_to('index');
600 });
601 }
602
603 1;
604
605 The "startup" method gets called right after instantiation and is the
606 place where the whole application gets set up. Since full Mojolicious
607 applications can use nested routes they have no need for "group"
608 blocks.
609
610 Simplified application script
611 "myapp.pl" itself can now be turned into a simplified application
612 script to allow running tests again.
613
614 #!/usr/bin/env perl
615
616 use strict;
617 use warnings;
618
619 use lib qw(lib);
620 use Mojolicious::Commands;
621
622 # Start command line interface for application
623 Mojolicious::Commands->start_app('MyApp');
624
625 And the directory structure of our hybrid application should be looking
626 like this.
627
628 myapp
629 |- myapp.pl
630 |- lib
631 | |- MyApp.pm
632 | +- MyApp
633 | +- Model
634 | +- Users.pm
635 |- t
636 | +- login.t
637 +- templates
638 |- layouts
639 | +- default.html.ep
640 |- index.html.ep
641 +- protected.html.ep
642
643 Controller class
644 Hybrid routes are a nice intermediate step, but to maximize
645 maintainability it makes sense to split our action code from its
646 routing information.
647
648 $ mkdir lib/MyApp/Controller
649 $ touch lib/MyApp/Controller/Login.pm
650 $ chmod 644 lib/MyApp/Controller/Login.pm
651
652 Once again the actual action code does not need to change, we just
653 rename $c to $self since the controller is now the invocant.
654
655 package MyApp::Controller::Login;
656 use Mojo::Base 'Mojolicious::Controller';
657
658 sub index {
659 my $self = shift;
660
661 my $user = $self->param('user') || '';
662 my $pass = $self->param('pass') || '';
663 return $self->render unless $self->users->check($user, $pass);
664
665 $self->session(user => $user);
666 $self->flash(message => 'Thanks for logging in.');
667 $self->redirect_to('protected');
668 }
669
670 sub logged_in {
671 my $self = shift;
672 return 1 if $self->session('user');
673 $self->redirect_to('index');
674 return undef;
675 }
676
677 sub logout {
678 my $self = shift;
679 $self->session(expires => 1);
680 $self->redirect_to('index');
681 }
682
683 1;
684
685 All Mojolicious::Controller controllers are plain old Perl classes and
686 get instantiated on demand.
687
688 Application class
689 The application class "lib/MyApp.pm" can now be reduced to model and
690 routing information.
691
692 package MyApp;
693 use Mojo::Base 'Mojolicious';
694
695 use MyApp::Model::Users;
696
697 sub startup {
698 my $self = shift;
699
700 $self->secrets(['Mojolicious rocks']);
701 $self->helper(users => sub { state $users = MyApp::Model::Users->new });
702
703 my $r = $self->routes;
704 $r->any('/')->to('login#index')->name('index');
705
706 my $logged_in = $r->under('/')->to('login#logged_in');
707 $logged_in->get('/protected')->to('login#protected');
708
709 $r->get('/logout')->to('login#logout');
710 }
711
712 1;
713
714 The router allows many different route variations,
715 Mojolicious::Guides::Routing explains them all in great detail.
716
717 Templates
718 Templates are our views, and usually bound to controllers, so they need
719 to be moved into the appropriate directories.
720
721 $ mkdir templates/login
722 $ mv templates/index.html.ep templates/login/index.html.ep
723 $ mv templates/protected.html.ep templates/login/protected.html.ep
724
725 Script
726 Finally "myapp.pl" can be moved into a "script" directory and renamed
727 to "my_app" to follow the CPAN standard.
728
729 $ mkdir script
730 $ mv myapp.pl script/my_app
731
732 Just a few small details change, instead of a relative path to lib we
733 now use Mojo::File to get an absolute path, allowing us to start the
734 application from outside its home directory.
735
736 #!/usr/bin/env perl
737
738 use strict;
739 use warnings;
740
741 use Mojo::File qw(curfile);
742 use lib curfile->dirname->sibling('lib')->to_string;
743 use Mojolicious::Commands;
744
745 # Start command line interface for application
746 Mojolicious::Commands->start_app('MyApp');
747
748 Simplified tests
749 Full Mojolicious applications are a little easier to test, so
750 "t/login.t" can be simplified.
751
752 use Test::More;
753 use Test::Mojo;
754
755 # Load application class
756 my $t = Test::Mojo->new('MyApp');
757 $t->ua->max_redirects(1);
758
759 $t->get_ok('/')
760 ->status_is(200)
761 ->element_exists('form input[name="user"]')
762 ->element_exists('form input[name="pass"]')
763 ->element_exists('form input[type="submit"]');
764
765 $t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
766 ->status_is(200)
767 ->text_like('html body' => qr/Welcome sebastian/);
768
769 $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
770
771 $t->get_ok('/logout')
772 ->status_is(200)
773 ->element_exists('form input[name="user"]')
774 ->element_exists('form input[name="pass"]')
775 ->element_exists('form input[type="submit"]');
776
777 done_testing();
778
779 And our final directory structure should be looking like this.
780
781 myapp
782 |- script
783 | +- my_app
784 |- lib
785 | |- MyApp.pm
786 | +- MyApp
787 | |- Controller
788 | | +- Login.pm
789 | +- Model
790 | +- Users.pm
791 |- t
792 | +- login.t
793 +- templates
794 |- layouts
795 | +- default.html.ep
796 +- login
797 |- index.html.ep
798 +- protected.html.ep
799
800 Test-driven development takes a little getting used to, but can be a
801 very powerful tool.
802
804 You can continue with Mojolicious::Guides now or take a look at the
805 Mojolicious wiki <http://github.com/mojolicious/mojo/wiki>, which
806 contains a lot more documentation and examples by many different
807 authors.
808
810 If you have any questions the documentation might not yet answer, don't
811 hesitate to ask on the mailing list
812 <http://groups.google.com/group/mojolicious> or the official IRC
813 channel "#mojo" on "irc.freenode.net" (chat now!
814 <https://webchat.freenode.net/#mojo>).
815
816
817
818perl v5.32.0 2020-07-28 Mojolicious::Guides::Growing(3)