Create User Registration in Mojolicious

Posted by Ashutosh on March 29, 2020


Hello Mojo Learners, in this article we learn, how to create a user registration process,
in the Mojolicious Framework.

PreRequisites

Before proceeding, there are some prerequisites:

  1. Perl elementary knowledge required.
  2. Understanding of Mojolicious
  3. Familiarity with the DBIx.
  4. dbicdump must be installed on your system.
  5. Mojolicious, Perl and DBIx installed on your system.
  6. MySQL must be installed on your system.
  7. Familiarity with MySQL basic commands.

If you are not familiar with anyone of the above, then I recommend to get required
elementary knowledge.

In my previous article, we covered how to setup and add the MySQL and DBIx::Class
to our Mojo App. Please visit here to set up the MySQL and DBIx;

We already have the MySQL setup in our previous article. To register the user,
we must create the user table first.

CREATE TABLE myApp_database.user (
  `id` bigint NOT NULL,
  `status` tinyint DEFAULT '1',
  `email` varchar(120) NOT NULL,
  `password` varchar(135) DEFAULT NULL,
  `first_name` varchar(80) DEFAULT NULL,
  `middle_name` varchar(80) DEFAULT NULL,
  `last_name` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

Once the commands execute successfully. It's time to add users to the tables.
There are two ways to do it:

  1. Run the Insert command.
  2. Create a user registration in Mojolicious.

We follow the later and create a user registration process in the Mojo framework.

Let's think about the steps that we need to follow to create the user registration process:

  1. We need to register a route first to open the registration form.
  2. We need to create a Controller to accommodate the route.
  3. User registration template.
  4. We need to create a Controller to post the user registration data.
  5. Ways to encrypt the password.

To start with, I think we are ok.

Start with the first point and create a new route to open the registration form.

Go to the MyApp.pm and in the startup subroutine register the route.

$r->get('/register')->to(
	controller => 'RegistrationController', action => 'register'
);

It says, when a user hits/register route, it goes to the RegistrationController and
looks out for the register subroutine. The problem is we don't have the
RegistrationController yet. Create it under the Controller folder.

package MyApp::Controller::RegistrationController;
use Mojo::Base 'Mojolicious::Controller';

sub register {
    my $c = shift;
    $c->render(template => 'register')
}

1;

In the register subroutine, $c is the default variable and we render the template register
under the templates folder. Repeatedly we have not created the register template yet.
Before creating 'register.html.ep' file go to the default.html.ep under the layouts and add bootstrap 4 files in it.

<!DOCTYPE html>
<html>

<head>
    <title><%= title %></title>

    <link rel="stylesheet" 
        href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" 
        integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" 
        crossorigin="anonymous" 
    />

</head>

<body>
    <%= content %>

    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
        integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
        crossorigin="anonymous">
    </script>
    
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
        crossorigin="anonymous">
    </script>
    
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
        integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
        crossorigin="anonymous">
    </script>
</body>

</html>

 

Now create the register.html.ep file.

% layout 'default';
<br /> <br />
<div class="container">
    <div class="card col-sm-6 mx-auto">
        <div class="card-header text-center">
            User Registration Form
        </div>
        <br /> <br />
        <form method="post" action='/register'>
            <input class="form-control" 
                   id="username" 
                   name="username" 
                   type="email" size="40"
                   placeholder="Enter Username" 
             />
            <br /> <br />
            <input class="form-control" 
                   id="password" 
                   name="password" 
                   type="password" 
                   size="40" 
                   placeholder="Enter Password" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="confirm_password" 
                   name="confirm_password" 
                   type="password" 
                   size="40" 
                   placeholder="Confirm Password" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="first_name" 
                   name="firstName" 
                   type="password" 
                   size="40" 
                   placeholder="User's First Name" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="middle_name" 
                   name="middleName" 
                   type="password" 
                   size="40" 
                   placeholder="User's Middle Name" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="username" 
                   name="lastName" 
                   type="password" 
                   size="40" 
                   placeholder="User's Last Name" 
             />   
            <br /> <br />
            <input class="btn btn-primary" type="submit" value="Register">
            <br />  <br />
        </form>
      % if ($error) {
            <div class="error" style="color: red">
                <small> <%= $error %> </small>
            </div>
        %}

        % if ($message) {
            <div class="error" style="color: green">
                <small> <%= $message %> </small>
            </div>
        %}
    </div>
        
</div>

 

$error and $message are the variables that we will pass to the register template.
Modify the register subroutine and add the following:

sub register {
    my $c = shift;
    
    $c->render(
        template => 'register',
        error    => $c->flash('error'),
        message  => $c->flash('message')
    );
}

 

When you visit the browser and hit localhost:3000/register. You see the following screen.

User Registration Form

We are doing good as of now.

What we have done so far is to show the user registration page. Now we need to create
a post route to submit the form. Go to the MyApp.pm file and add the following under
the startup subroutine.

$r->post('/register')->to(
        controller => 'RegistrationController',
        action     => 'user_registration'
);

In the RegistrationController, add the subroutine user_registration

sub user_registration {
    my $c = shift;

    my $email = $c->param('username');
    my $password = $c->param('password');
    my $confirm_password = $c->param('confirm_password');
    my $first_name = $c->param('firstName');
    my $middle_name = $c->param('middleName');
    my $last_name = $c->param('lastName');

    if (! $email || ! $password || ! $confirm_password || ! $first_name || ! $last_name) {
        $c->flash( error => 'Username, Password, First Name and Last Name are the mandatory fields.');
        $c->redirect_to('register');
    }

    if ($password ne $confirm_password) {
        $c->flash( error => 'Password and Confirm Password must be same.');
        $c->redirect_to('register');
    }

    my $dbh = $c->app->{_dbh};
    
    my $user = $dbh->resultset('User')->search({ email => $email });

    if (! $user->first ) {
        eval {
            $dbh->resultset('User')->create({ 
                email       => $email,
                password    => generate_password($password),
                first_name  => $first_name,
                middle_name => $middle_name,
                last_name   => $last_name,
                status      => 1
            });
        };
        if ($@) {
            $c->flash( error => 'Error in db query. Please check mysql logs.');
            $c->redirect_to('register');
        }
        else {
            $c->flash( message => 'User added to the database successfully.');
            $c->redirect_to('register');
        }
    }
    else {
        $c->flash( error => 'Username already exists.');
        $c->redirect_to('register');
    }
}

sub generate_password {
    my $password = shift;

    my $pbkdf2 = Crypt::PBKDF2->new(
        hash_class => 'HMACSHA1', 
        iterations => 1000,       
        output_len => 20,         
        salt_len   => 4,         
    );

    return $pbkdf2->generate($password);
}

And don't forgot to import the use Crypt::PBKDF2; in the RegistrationController.

Visit the URL http://localhost:3000/register and enter the required details and hit the
register button and in the button of the screen you will see the message
'User added to the database successfully'.

In this article, we learn how to register the user in the Mojolicious App.
Offcourse it is not production ready and we need to add some more validation like Validate Email,
Password length from the user. Apart from it, we should make sure that User's First Name and
Last Name shouldn't take any invalid characters like "@%$#^&*".

Here you can do something like this to validate an email. This should make sure that user
enters the valid email address. Not only this, it should also check whether the
extension like foo.com is valid exchanger or not. You can use this to integrate this in your app.

sub validate_email {
    my ($self, $email) = @_;

    return (
        Email::Valid->address(  
            -address => $email,  -mxcheck => 1 
        ) ? 1 : 0
    );
}

And to validate the user's First Name, Middle and Last Name:

sub validate_name {
    my ($self,  $name) = @_;
    
    if ($name =~ /^[a-zA-Z]+$/ ) {
        return 1;
    }
    else {  
        return 0;
    }
}

You can use any method to validate just to keep spammers out of your way to input the junk into your database.

We can also use JWT, to confirm the user by sending the encrypted text via email
and when user clicks it, then we confirm the user. It will be covered in a separate article someday.

Till then good bye and love Mojolicious.