The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Mojolicious::Guides::Growing - Growing

OVERVIEW

Starting a Mojolicious::Lite prototype from scratch and growing it into a well structured Mojolicious application.

CONCEPTS

Essentials every Mojolicious developer should know.

Model View Controller

MVC is a software architectual pattern for graphical user iterface programming, originating in Smalltalk-80 that separates application logic, presentation and input.

               .------------.    .-------.    .------.
    Request -> | Controller | -> | Model | -> | View | -> Response
               '------------'    '-------'    '------'

A slightly modified version of the pattern moving some application logic into the controller is the foundation of pretty much every web framework these days.

                .----------------.     .-------.
    Request  -> |                | <-> | Model |
                |                |     '-------'
                |   Controller   |
                |                |     .-------.
    Response <- |                | <-> | View  |
                '----------------'     '-------'

The controller receives a request from a user, passes incoming data to the model and retrieves outgoing data from it, which then gets turned into an actual response by the view. It's important to consider that this pattern is just a guideline that most of the time results in cleaner more maintainable code, but not a rule that should be followed at all costs.

PROTOTYPE

One of the main differences between Mojolicious and other web frameworks is that it also includes Mojolicious::Lite, a micro web framework optimized for rapid prototyping.

Foundation

A Mojolicious::Lite protoype often consists of just a single executable Perl script like myapp.pl.

    % mkdir myapp
    % cd myapp
    % touch myapp.pl
    % chmod 744 myapp.pl

This will be the foundation for our login manager example application.

    use Mojolicious::Lite;

    get '/' => sub {
        my $self = shift;
        $self->render(text => 'Hello world!');
    };

    app->start;

The built in web server makes working on your application a lot of fun thanks to automatic reloading.

    % ./myapp.pl daemon --reload
    Server available at http://*:3000.

Just save your changes and they will be automatically in effect the next time you refresh your browser.

Model

In Mojolicious we consider web applications simple frontends for existing application logic, that means Mojolicious is entirely model layer agnostic and you just use whatever Perl modules you like most.

    % mkdir lib
    % touch lib/MyUser.pm
    % chmod 644 lib/MyUser.pm

Our login manager will simply use a normal Perl module abstracting away all logic related to matching user names and passwords.

    package MyUsers;

    use strict;
    use warnings;

    my $USERS = {
        sri => 'secr3t',
        vti => 'lulz',
        yko => 'zeecaptain'
    };

    sub new { bless {}, shift }

    sub check {
        my ($self, $user, $pass) = @_;

        # Success
        return 1 if $USERS->{$user} && $USERS->{$user} eq $pass;

        # Fail
        return;
    }

    1;

The defaults method of the application can be used to make our model available to all actions through the stash.

    use Mojolicious::Lite;

    use lib 'lib';
    use MyUsers;

    app->defaults(users => MyUsers->new);

    # GET /?user=sri&pass=secr3t
    any '/' => sub {
        my $self = shift;

        # Query parameters
        my $user = $self->param('user');
        my $pass = $self->param('pass');

        # Check password
        return $self->render(text => "Welcome $user!")
          if $self->stash('users')->check($user, $pass);

        # Failed
        $self->render(text => 'Wrong username or password!');
    };

    app->start;

The param method of our Mojolicious::Controller instance is used to access query parameters, POST parameters and route placeholders, all at once.

Testing

Test driven development is a very powerful tool for designing good APIs, because you are building a use case before the actual implementation.

    % mkdir t
    % touch t/login.t
    % chmod 644 t/login.t

Test::Mojo is a scriptable user agent designed specifically for testing with many fun state of the art features such as CSS3 selectors.

    use Test::More tests => 16;
    use Test::Mojo;

    # Include application
    use FindBin;
    $ENV{MOJO_HOME} = "$FindBin::Bin/../";
    require "$ENV{MOJO_HOME}/myapp.pl";

    my $t = Test::Mojo->new(max_redirects => 1);

    # GET / (login form)
    $t->get_ok('/')->status_is(200)
      ->element_exists('form input[name="user"]')
      ->element_exists('form input[name="pass"]')
      ->element_exists('form input[type="submit"]');

    # POST / (submit login form)
    $t->post_form_ok('/' => {user => 'sri', pass => 'secr3t'})
      ->status_is(200)->text_like('html body' => qr/Welcome sri/);

    # GET /protected (member only page)
    $t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);

    # GET /logout (back to the login form)
    $t->get_ok('/logout')->status_is(200)
      ->element_exists('form input[name="user"]')
      ->element_exists('form input[name="pass"]')
      ->element_exists('form input[type="submit"]');

From now on you can always check your progress by running these unit tests against your application.

    % ./myapp.pl test
    ...
    % perl t/login.pl
    ...

To make the test less noisy and limit log output to just error messages you could add a line like this.

    $t->app->log->level('error');

You can also make quick GET requests from the command line.

    % ./myapp.pl get /
    Wrong username or password!

    % ./myapp.pl get -v /?user=sri&pass=secr3t
    HTTP/1.1 200 OK
    Connection: Keep-Alive
    Date: Sun, 18 Jul 2010 13:09:58 GMT
    Server: Mojolicious (Perl)
    Content-Length: 12
    Content-Type: text/plain

    Welcome sri!

Final Prototype

A final prototype for our login manager could look like this.

    use Mojolicious::Lite;

    use lib 'lib';
    use MyUsers;

    app->defaults(users => MyUsers->new);

    any '/' => sub {
        my $self = shift;

        # Query or POST parameters
        my $user = $self->param('user') || '';
        my $pass = $self->param('pass') || '';

        # Check password
        return $self->render
          unless $self->stash('users')->check($user, $pass);

        # Store user name in session
        $self->session(user => $user);

        # Store a friendly message for the next page in flash
        $self->flash(message => 'Thanks for logging in!');

        # Redirect to protected page
        $self->redirect_to('protected');
    } => 'index';

    get '/protected' => sub {
        my $self = shift;

        # Redirect to main page if user is not logged in
        return $self->redirect_to('index') unless $self->session('user');
    } => 'protected';

    get '/logout' => sub {
        my $self = shift;

        # Expire and in turn clear session automatically
        $self->session(expires => 1);

        # Redirect to main page
        $self->redirect_to('index');
    } => 'logout';

    app->start;
    __DATA__

    @@ layouts/default.html.ep
    <!doctype html><html>
        <head><title>Login Manager</title></head>
        <body><%= content %></body>
    </html>

    @@ index.html.ep
    % layout 'default';
    <%= form_for index => {%>
        <% if (param 'user') { %>
            <b>Wrong name or password, please try again.</b><br />
        <% } %>
        Name:<br />
        <%= input user => (type => 'text') %><br />
        Password:<br />
        <%= input pass => (type => 'text') %><br />
        <input type="submit" value="Login" />
    <%}%>

    @@ protected.html.ep
    % layout 'default';
    <% if (my $message = flash 'message' ) { %>
        <b><%= $message %></b><br />
    <% } %>
    Welcome <%= session 'user' %>!<br />
    <%= link_to logout => {%>
        Logout
    <%}%>