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.
12

CONCEPTS

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

PROTOTYPE

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

WELL-STRUCTURED APPLICATION

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

MORE

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

SUPPORT

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