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