1Mojolicious::Guides::GrUosweirngC(o3n)tributed Perl DocuMmoejnotlaitciioonus::Guides::Growing(3)
2
3
4

NAME

6       Mojolicious::Guides::Growing - Growing Mojolicious applications
7

OVERVIEW

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

CONCEPTS

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

PROTOTYPE

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

WELL-STRUCTURED APPLICATION

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

MORE

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

SUPPORT

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)
Impressum