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 -signatures;
185
186 get '/' => sub ($c) {
187 $c->render(text => 'Hello World!');
188 };
189
190 app->start;
191
192 The built-in development web server makes working on your application a
193 lot of fun thanks to automatic reloading.
194
195 $ morbo ./myapp.pl
196 Web application available at http://127.0.0.1:3000
197
198 Just save your changes and they will be automatically in effect the
199 next time you refresh your browser.
200
201 A bird's-eye view
202 It all starts with an HTTP request like this, sent by your browser.
203
204 GET / HTTP/1.1
205 Host: localhost:3000
206
207 Once the request has been received by the web server through the event
208 loop, it will be passed on to Mojolicious, where it will be handled in
209 a few simple steps.
210
211 1.
212 Check if a static file exists that would meet the requirements.
213
214 2.
215 Try to find a route that would meet the requirements.
216
217 3.
218 Dispatch the request to this route, usually reaching one or more
219 actions.
220
221 4.
222 Process the request, maybe generating a response with the renderer.
223
224 5.
225 Return control to the web server, and if no response has been
226 generated yet, wait for a non-blocking operation to do so through the
227 event loop.
228
229 With our application the router would have found an action in step 2,
230 and rendered some text in step 4, resulting in an HTTP response like
231 this being sent back to the browser.
232
233 HTTP/1.1 200 OK
234 Content-Length: 12
235 Hello World!
236
237 Model
238 In Mojolicious we consider web applications simple frontends for
239 existing business logic, that means Mojolicious is by design entirely
240 model layer agnostic and you just use whatever Perl modules you like
241 most.
242
243 $ mkdir -p lib/MyApp/Model
244 $ touch lib/MyApp/Model/Users.pm
245 $ chmod 644 lib/MyApp/Model/Users.pm
246
247 Our login manager will simply use a plain old Perl module abstracting
248 away all logic related to matching usernames and passwords. The name
249 "MyApp::Model::Users" is an arbitrary choice, and is simply used to
250 make the separation of concerns more visible.
251
252 package MyApp::Model::Users;
253
254 use strict;
255 use warnings;
256 use experimental qw(signatures);
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 ($class) { bless {}, $class }
267
268 sub check ($self, $user, $pass) {
269
270 # Success
271 return 1 if $USERS->{$user} && secure_compare $USERS->{$user}, $pass;
272
273 # Fail
274 return undef;
275 }
276
277 1;
278
279 A simple helper can be registered with the function "helper" in
280 Mojolicious::Lite to make our model available to all actions and
281 templates.
282
283 #!/usr/bin/env perl
284 use Mojolicious::Lite -signatures;
285
286 use lib qw(lib);
287 use MyApp::Model::Users;
288
289 # Helper to lazy initialize and store our model object
290 helper users => sub { state $users = MyApp::Model::Users->new };
291
292 # /?user=sebastian&pass=secr3t
293 any '/' => sub ($c) {
294
295 # Query parameters
296 my $user = $c->param('user') || '';
297 my $pass = $c->param('pass') || '';
298
299 # Check password
300 return $c->render(text => "Welcome $user.") if $c->users->check($user, $pass);
301
302 # Failed
303 $c->render(text => 'Wrong username or password.');
304 };
305
306 app->start;
307
308 The method "param" in Mojolicious::Controller is used to access query
309 parameters, "POST" parameters, file uploads and route placeholders, all
310 at once.
311
312 Testing
313 In Mojolicious we take testing very serious and try to make it a
314 pleasant experience.
315
316 $ mkdir t
317 $ touch t/login.t
318 $ chmod 644 t/login.t
319
320 Test::Mojo is a scriptable HTTP user agent designed specifically for
321 testing, with many fun state of the art features such as CSS selectors
322 based on Mojo::DOM.
323
324 use Test::More;
325 use Test::Mojo;
326
327 # Include application
328 use Mojo::File qw(curfile);
329 require(curfile->dirname->sibling('myapp.pl'));
330
331 # Allow 302 redirect responses
332 my $t = Test::Mojo->new;
333 $t->ua->max_redirects(1);
334
335 # Test if the HTML login form exists
336 $t->get_ok('/')
337 ->status_is(200)
338 ->element_exists('form input[name="user"]')
339 ->element_exists('form input[name="pass"]')
340 ->element_exists('form input[type="submit"]');
341
342 # Test login with valid credentials
343 $t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
344 ->status_is(200)
345 ->text_like('html body' => qr/Welcome sebastian/);
346
347 # Test accessing a protected page
348 $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
349
350 # Test if HTML login form shows up again after logout
351 $t->get_ok('/logout')
352 ->status_is(200)
353 ->element_exists('form input[name="user"]')
354 ->element_exists('form input[name="pass"]')
355 ->element_exists('form input[type="submit"]');
356
357 done_testing();
358
359 Your application won't pass these tests, but from now on you can use
360 them to check your progress.
361
362 $ prove -l
363 $ prove -l t/login.t
364 $ prove -l -v t/login.t
365
366 Or perform quick requests right from the command line with
367 Mojolicious::Command::get.
368
369 $ ./myapp.pl get /
370 Wrong username or password.
371
372 $ ./myapp.pl get -v '/?user=sebastian&pass=secr3t'
373 GET /?user=sebastian&pass=secr3t HTTP/1.1
374 User-Agent: Mojolicious (Perl)
375 Accept-Encoding: gzip
376 Content-Length: 0
377 Host: localhost:59472
378
379 HTTP/1.1 200 OK
380 Date: Sun, 18 Jul 2010 13:09:58 GMT
381 Server: Mojolicious (Perl)
382 Content-Length: 12
383 Content-Type: text/plain
384
385 Welcome sebastian.
386
387 State keeping
388 Sessions in Mojolicious pretty much just work out of the box once you
389 start using the method "session" in Mojolicious::Controller, there is
390 no setup required, but we suggest setting a more secure passphrase with
391 "secrets" in Mojolicious.
392
393 $app->secrets(['Mojolicious rocks']);
394
395 This passphrase is used by the HMAC-SHA1 algorithm to make signed
396 cookies tamper resistant and can be changed at any time to invalidate
397 all existing sessions.
398
399 $c->session(user => 'sebastian');
400 my $user = $c->session('user');
401
402 By default all sessions expire after one hour, for more control you can
403 use the "expiration" session value to set an expiration date in seconds
404 from now.
405
406 $c->session(expiration => 3600);
407
408 And the whole session can be deleted by using the "expires" session
409 value to set an absolute expiration date in the past.
410
411 $c->session(expires => 1);
412
413 For data that should only be visible on the next request, like a
414 confirmation message after a 302 redirect performed with "redirect_to"
415 in Mojolicious::Plugin::DefaultHelpers, you can use the flash,
416 accessible through "flash" in Mojolicious::Plugin::DefaultHelpers.
417
418 $c->flash(message => 'Everything is fine.');
419 $c->redirect_to('goodbye');
420
421 Just remember that all session data gets serialized with Mojo::JSON and
422 stored in HMAC-SHA1 signed cookies, which usually have a 4096 byte
423 (4KiB) limit, depending on browser.
424
425 Final prototype
426 A final "myapp.pl" prototype passing all of the tests above could look
427 like this.
428
429 #!/usr/bin/env perl
430 use Mojolicious::Lite -signatures;
431
432 use lib qw(lib);
433 use MyApp::Model::Users;
434
435 # Make signed cookies tamper resistant
436 app->secrets(['Mojolicious rocks']);
437
438 helper users => sub { state $users = MyApp::Model::Users->new };
439
440 # Main login action
441 any '/' => sub ($c) {
442
443 # Query or POST parameters
444 my $user = $c->param('user') || '';
445 my $pass = $c->param('pass') || '';
446
447 # Check password and render "index.html.ep" if necessary
448 return $c->render unless $c->users->check($user, $pass);
449
450 # Store username in session
451 $c->session(user => $user);
452
453 # Store a friendly message for the next page in flash
454 $c->flash(message => 'Thanks for logging in.');
455
456 # Redirect to protected page with a 302 response
457 $c->redirect_to('protected');
458 } => 'index';
459
460 # Make sure user is logged in for actions in this group
461 group {
462 under sub ($c) {
463
464 # Redirect to main page with a 302 response if user is not logged in
465 return 1 if $c->session('user');
466 $c->redirect_to('index');
467 return undef;
468 };
469
470 # A protected page auto rendering "protected.html.ep"
471 get '/protected';
472 };
473
474 # Logout action
475 get '/logout' => sub ($c) {
476
477 # Expire and in turn clear session automatically
478 $c->session(expires => 1);
479
480 # Redirect to main page with a 302 response
481 $c->redirect_to('index');
482 };
483
484 app->start;
485 __DATA__
486
487 @@ index.html.ep
488 % layout 'default';
489 %= form_for index => begin
490 % if (param 'user') {
491 <b>Wrong name or password, please try again.</b><br>
492 % }
493 Name:<br>
494 %= text_field 'user'
495 <br>Password:<br>
496 %= password_field 'pass'
497 <br>
498 %= submit_button 'Login'
499 % end
500
501 @@ protected.html.ep
502 % layout 'default';
503 % if (my $msg = flash 'message') {
504 <b><%= $msg %></b><br>
505 % }
506 Welcome <%= session 'user' %>.<br>
507 %= link_to Logout => 'logout'
508
509 @@ layouts/default.html.ep
510 <!DOCTYPE html>
511 <html>
512 <head><title>Login Manager</title></head>
513 <body><%= content %></body>
514 </html>
515
516 And the directory structure should be looking like this now.
517
518 myapp
519 |- myapp.pl
520 |- lib
521 | +- MyApp
522 | +- Model
523 | +- Users.pm
524 +- t
525 +- login.t
526
527 Our templates are using quite a few features of the renderer,
528 Mojolicious::Guides::Rendering explains them all in great detail.
529
531 Due to the flexibility of Mojolicious there are many variations of the
532 actual growing process, but this should give you a good overview of the
533 possibilities.
534
535 Inflating templates
536 All templates and static files inlined in the "DATA" section can be
537 automatically turned into separate files in the "templates" and
538 "public" directories with the command
539 Mojolicious::Command::Author::inflate.
540
541 $ ./myapp.pl inflate
542
543 Those directories have a higher precedence, so inflating can also be a
544 great way to allow your users to customize their applications.
545
546 Simplified application class
547 This is the heart of every full Mojolicious application and always gets
548 instantiated during server startup.
549
550 $ touch lib/MyApp.pm
551 $ chmod 644 lib/MyApp.pm
552
553 We will start by extracting all actions from "myapp.pl" and turn them
554 into simplified hybrid routes in the Mojolicious::Routes router, none
555 of the actual action code needs to be changed.
556
557 package MyApp;
558 use Mojo::Base 'Mojolicious', -signatures;
559
560 use MyApp::Model::Users;
561
562 sub startup ($self) {
563
564 $self->secrets(['Mojolicious rocks']);
565 $self->helper(users => sub { state $users = MyApp::Model::Users->new });
566
567 my $r = $self->routes;
568
569 $r->any('/' => sub ($c) {
570
571 my $user = $c->param('user') || '';
572 my $pass = $c->param('pass') || '';
573 return $c->render unless $c->users->check($user, $pass);
574
575 $c->session(user => $user);
576 $c->flash(message => 'Thanks for logging in.');
577 $c->redirect_to('protected');
578 } => 'index');
579
580 my $logged_in = $r->under(sub ($c) {
581 return 1 if $c->session('user');
582 $c->redirect_to('index');
583 return undef;
584 });
585 $logged_in->get('/protected');
586
587 $r->get('/logout' => sub ($c) {
588 $c->session(expires => 1);
589 $c->redirect_to('index');
590 });
591 }
592
593 1;
594
595 The "startup" method gets called right after instantiation and is the
596 place where the whole application gets set up. Since full Mojolicious
597 applications can use nested routes they have no need for "group"
598 blocks.
599
600 Simplified application script
601 "myapp.pl" itself can now be turned into a simplified application
602 script to allow running tests again.
603
604 #!/usr/bin/env perl
605
606 use Mojo::Base -strict;
607 use lib qw(lib);
608 use Mojolicious::Commands;
609
610 # Start command line interface for application
611 Mojolicious::Commands->start_app('MyApp');
612
613 And the directory structure of our hybrid application should be looking
614 like this.
615
616 myapp
617 |- myapp.pl
618 |- lib
619 | |- MyApp.pm
620 | +- MyApp
621 | +- Model
622 | +- Users.pm
623 |- t
624 | +- login.t
625 +- templates
626 |- layouts
627 | +- default.html.ep
628 |- index.html.ep
629 +- protected.html.ep
630
631 Controller class
632 Hybrid routes are a nice intermediate step, but to maximize
633 maintainability it makes sense to split our action code from its
634 routing information.
635
636 $ mkdir lib/MyApp/Controller
637 $ touch lib/MyApp/Controller/Login.pm
638 $ chmod 644 lib/MyApp/Controller/Login.pm
639
640 Once again the actual action code does not need to change, we just
641 rename $c to $self since the controller is now the invocant.
642
643 package MyApp::Controller::Login;
644 use Mojo::Base 'Mojolicious::Controller', -signatures;
645
646 sub index ($self) {
647 my $user = $self->param('user') || '';
648 my $pass = $self->param('pass') || '';
649 return $self->render unless $self->users->check($user, $pass);
650
651 $self->session(user => $user);
652 $self->flash(message => 'Thanks for logging in.');
653 $self->redirect_to('protected');
654 }
655
656 sub logged_in ($self) {
657 return 1 if $self->session('user');
658 $self->redirect_to('index');
659 return undef;
660 }
661
662 sub logout ($self) {
663 $self->session(expires => 1);
664 $self->redirect_to('index');
665 }
666
667 1;
668
669 All Mojolicious::Controller controllers are plain old Perl classes and
670 get instantiated on demand.
671
672 Application class
673 The application class "lib/MyApp.pm" can now be reduced to model and
674 routing information.
675
676 package MyApp;
677 use Mojo::Base 'Mojolicious', -signatures;
678
679 use MyApp::Model::Users;
680
681 sub startup ($self) {
682
683 $self->secrets(['Mojolicious rocks']);
684 $self->helper(users => sub { state $users = MyApp::Model::Users->new });
685
686 my $r = $self->routes;
687 $r->any('/')->to('login#index')->name('index');
688
689 my $logged_in = $r->under('/')->to('login#logged_in');
690 $logged_in->get('/protected')->to('login#protected');
691
692 $r->get('/logout')->to('login#logout');
693 }
694
695 1;
696
697 The router allows many different route variations,
698 Mojolicious::Guides::Routing explains them all in great detail.
699
700 Templates
701 Templates are our views, and usually bound to controllers, so they need
702 to be moved into the appropriate directories.
703
704 $ mkdir templates/login
705 $ mv templates/index.html.ep templates/login/index.html.ep
706 $ mv templates/protected.html.ep templates/login/protected.html.ep
707
708 Script
709 Finally "myapp.pl" can be moved into a "script" directory and renamed
710 to "my_app" to follow the CPAN standard.
711
712 $ mkdir script
713 $ mv myapp.pl script/my_app
714
715 Just a few small details change, instead of a relative path to lib we
716 now use Mojo::File to get an absolute path, allowing us to start the
717 application from outside its home directory.
718
719 #!/usr/bin/env perl
720
721 use strict;
722 use warnings;
723
724 use Mojo::File qw(curfile);
725 use lib curfile->dirname->sibling('lib')->to_string;
726 use Mojolicious::Commands;
727
728 # Start command line interface for application
729 Mojolicious::Commands->start_app('MyApp');
730
731 Simplified tests
732 Full Mojolicious applications are a little easier to test, so
733 "t/login.t" can be simplified.
734
735 use Mojo::Base -strict;
736
737 use Test::More;
738 use Test::Mojo;
739
740 my $t = Test::Mojo->new('MyApp');
741 $t->ua->max_redirects(1);
742
743 subtest 'Test login workflow' => sub {
744 $t->get_ok('/')
745 ->status_is(200)
746 ->element_exists('form input[name="user"]')
747 ->element_exists('form input[name="pass"]')
748 ->element_exists('form input[type="submit"]');
749
750 $t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
751 ->status_is(200)
752 ->text_like('html body' => qr/Welcome sebastian/);
753
754 $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
755
756 $t->get_ok('/logout')
757 ->status_is(200)
758 ->element_exists('form input[name="user"]')
759 ->element_exists('form input[name="pass"]')
760 ->element_exists('form input[type="submit"]');
761 };
762
763 done_testing();
764
765 And our final directory structure should be looking like this.
766
767 myapp
768 |- script
769 | +- my_app
770 |- lib
771 | |- MyApp.pm
772 | +- MyApp
773 | |- Controller
774 | | +- Login.pm
775 | +- Model
776 | +- Users.pm
777 |- t
778 | +- login.t
779 +- templates
780 |- layouts
781 | +- default.html.ep
782 +- login
783 |- index.html.ep
784 +- protected.html.ep
785
786 Test-driven development takes a little getting used to, but can be a
787 very powerful tool.
788
790 You can continue with Mojolicious::Guides now or take a look at the
791 Mojolicious wiki <https://github.com/mojolicious/mojo/wiki>, which
792 contains a lot more documentation and examples by many different
793 authors.
794
796 If you have any questions the documentation might not yet answer, don't
797 hesitate to ask in the Forum <https://forum.mojolicious.org> or the
798 official IRC channel "#mojo" on "chat.freenode.net" (chat now!
799 <https://webchat.freenode.net/#mojo>).
800
801
802
803perl v5.32.1 2021-02-07 Mojolicious::Guides::Growing(3)