Masonite is a great framework to start building things if you are a beginner or a complete expert. So if you are here to get the basics to start building web applications with Masonite, then you are at the right place. For this tutorial, we will build a simple page to manage a bookstore with information such as the title and description of a book. The application allows basics CRUD operations such as create (add a book title and its description) read (listing the books), updating (Modify title or description) or delete. Before starting, I want to make some quick reminds. As Masonite is a MVC framework, it’s very important to understand the architecture of the project :

  • The Model stores data in the database. It’s the shape of the data and business logic.
  • The View is the UI. It basically consists of HTML/CSS/JavaScript and it renders the data returned by the Controller.
  • The Controller connects View and Model adequately. It handles any requests coming from the view, treats it and involves the adequate model if necessary.
  • Now, let’s start. First, you need to set up your environment. mkdir bookstore cd bookstore

Then, we need to create an isolated Python environment for our project.

virtualenv env
source env/bin/activate 

Great, the environment is ready. Let’s proceed to installation.

Installation

pip install masonite Masonite comes with a great tool named craft. You will see the power of this tool and how it can save time with the MVC architecture. Run craft to be sure that the command is available. To create the project, run : craft new bookstore . Don’t forget the . at the end. It will automatically generate the project in the current directory. Now run : craft serve

Setting Controllers

Controllers are important in Masonite. There are located in app/http/controllers If you are coming from Django, you can see it as a View. However here, Controllers are Python Classes and that’s the methods within that handle data or requests. Before creating a controller, you need first to define its route in routes/web.py.

"""Web Routes."""
#routes/web.py

from masonite.routes import Get, Post

ROUTES = [
    #Get().route('/', 'WelcomeController@show').name('welcome'),
    Get().route('/', 'BoookController@show').name('welcome'),
]

This done, we are going to use the craft command to simply generate .

#app/http/controllers/StoreController.py

"""A BookController Module.""" 

from masonite.request import Request
from masonite.view import View
from masonite.controllers import Controller


class BookController(Controller):
    """BookController Controller Class."""

    def __init__(self, request: Request):
        """BookController Initializer

        Arguments:
            request {masonite.request.Request} -- The Masonite Request class.
        """
        self.request = request

    def show(self, view: View):
        pass

The show method will simply return a view. A view here is simply the name of our template written in HTML.

...
 def show(self, view: View): 
        return view.render('books/store')

Create our first View

View handles the UI. They are HTML templates. They are located in resources/templates. But there will be times when you will have the same blocks into many pages. For example, you can have the same head tag for 4 or 5 pages. Masonite uses Jinja2 as a template engine, so we can use template inheritance functionality. We will create a base.html where we’ll put the blocks and the structure of every view on Masonite. craft view base

<!--resources/templates/base.html-->
<!DOCTYPE html>
<html lang="">
<head>
    {% block head %}
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Book Store</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <style>
        .footer {
            padding: 2.5rem 0;
            color: #999;
            text-align: center;
            border-top: .05rem solid #e5e5e5;
        }
        .has-item-centered {
            display: flex;
            justify-content: center;
        }
    </style>
    {% endblock %}
</head>
<body>
    <nav class="navbar navbar-light bg-success">
        {% block nav %}
        <span class="navbar-brand">
            <h1 class="text-white">
                Admin BookStore
            </h1>
        </span>
        {% endblock %}
    </nav>
    <!--Section-->
    <section class="container-fluid">
        {% block content %}
        {% endblock %}
    </section>

    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous">
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous">
    </script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous">
    </script>
</body>
</html>

To create a view for our store, you simply need to use craft again. craft view /book/store.html This command creates a view into /resources/templates/book/store.html . I provided a template about what you are going to write into.

<!--resources/templates/books/store.html-->
{% extends 'base.html' %} {% block content %}
<div class="container text-center">
  <span class="lead">
    Welcome to your panel admin. If you want to add a book,just follow
    instructions
  </span>
  <div class="row">
    <div class="col"></div>
    <div class="col-sm">
      <form action="{{ route('books.add') }}" method="POST">
        {{ csrf_field }}
        <div class="form-group">
          <input class="form-control" name="title" type="text" placeholder="Add Book title" />
        </div>
        <div class="form-group">
          <textarea class="form-control" name="description" type="text" placeholder="Description"></textarea>
        </div>
        <div class="form-group has-item-centered">
          <!--Add action-->
          <button class="btn btn-outline-success">
            Add Book
          </button>
        </div>
      </form>
    </div>
    <div class="col"></div>
  </div>
  <div class="row">
  </div>
</div>
<footer class="footer container-fluid">
  <p class="text-center">
    Made with <i class="fa fa-heart" aria-hidden="true"></i> by <a href="#">Kolawole Mangabo</a>
  </p>
</footer>
{% endblock %}

Be sure your server is running with craft serve and hit 127.0.0.1:8000 :

alt text

Books Model

We need to store information about books so we will create a model to handle it. First, we need to set up our databases and migrations. Be sure theses lines in your .env file look like this :

DB_CONNECTION=sqlite3
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=store.db
DB_USERNAME=root
DB_PASSWORD=root

Now, we will craft migrations for books tables.

Migrations

craft migration create_books_table --create books This command generate a recent migration file in databases/migrations/

def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('books') as table:
            table.increments('id')
            table.timestamps()

But we need to make some modifications,

        with self.schema.create('books') as table:
            table.increments('id')
            table.string('title') #New line
            table.string('description') #New line

            table.timestamps()

Now, we can run the migrations to create books tables : craft migrate

Books Model

Models in Masonite take shape of tables. craft model -m Book It will create a Model in app/Book.py.

"""Book Model."""
#app/Book.py
from config.database import Model
class Book(Model):
    """Book Model."""
    pass

It’s important to precise the table we want to connect the Model and the fillable inputs.

class Book(Model):
    """Book Model."""
    __table__ = 'books'
    __fillable__=['title','description']

Add Functionality

Create the add functionality requires first to precise the route in web.py.

#routes/web.py
...
 #Get().route('/', 'WelcomeController@show').name('welcome'),
    Get().route('/', 'BookController@show').name('welcome'),

    Post().route('/books/','BookController@store').name('book.store'),
    ]

Add Method

#app/http/controllers/StoreController.py
from app.Book import Book
...
    def show(self, view: View):
        return view.render('store')

    def store(self, request:Request):
        """Add a book and its description"""
        Book.create(
            title=request.input('title'),
            description=request.input('description')
        )

        return request.redirect('/')

Now it’s done, add information about 2 or 3 books.

Show the book’s information

To show information, we will use Jinja2 syntax to create a loop that will show information about each book. But first, we must retrieve these data from a method in our Controller.

#app/http/controllers/BookController.py
     def show(self, view: View):
        books = Book.all() #1
        return view.render('books/store',{'books':books}) #2

In line #1, we retrieve all entries available in the table through Book model. And then in 2, we create a dictionary and assign book to its value. Now, let’s create the loop block

<!--resources/template/books/store.html-->
         <!--Books card-->
    {% for book in books %}
    <div class="col-3 card" style="width: 18rem;">
      <article>
        <div class="card-body">
          <h3 class="card-title">{{ book.title }}</h3>
          <p class="card-text">
            <br />
            {{ book.description }}
          </p>
          <div>
            <!--Update Action-->
            <a class="btn btn-warning" href="{{ route('update', {'id': book.id }) }}">
              <span class="">Update
                <i class="fas fa-retweet" aria-hidden="true"></i>
              </span>
            </a>
            <!--Delete Action-->
            <a class="btn btn-danger" href="{{ route('delete', {'id' : book.id }) }}">
              <span class="">
                Delete
                <i class="fas fa-heart" aria-hidden="true"></i>
              </span>
            </a>
          </div>
        </div>
      </article>
    </div>
    {% endfor %}

Update and Delete

#routes/web.py 
...
    Get('/books/@id/delete','BookController@delete').name('delete'),

    Get('/books/@id/update','BookController@update').name('update'),
    Post('/books/@id/edit','Controller@edit').name('edit')
]

Update and Delete methods

#app/http/controllers/StoreController.py
...
    def delete(self, request:Request):
        book = Book.find(request.param('id'))
        book.delete()
        return request.redirect('/')

    def update(self, view: View, request: Request):
        book = Book.find(request.param('id'))

        return view.render('/book/update', {'book': book})

    def edit(self, request:Request):
        book = Book.find(request.param('id'))

        book.title = request.input('title')
        book.description = request.input('description')

        book.save()

        return request.redirect('/')

Now make the delete button and update button active :

<!--store.html-->
          <div>
              <!--Update Action-->
              <a class="button is-info" href="{{ route('update', {'id': book.id }) }}">
                <span class="">Update
                  <i class="fas fa-retweet" aria-hidden="true"></i>
                </span>
              </a>
              <!--Delete Action-->
              <a class="button is-danger" href="/{{ route('delete', {'id': book.id }) }}">
                <span class=""> Delete
                  <i class="fas fa-heart" aria-hidden="true"></i>
                </span>
              </a>
            </div>

Go ahead and craft view for update template. craft view /books/update

<!--update.html-->
{% extends '../base.html' %}
{% block content %}
<div class="container">
  <div class="row">
    <div class="col">
    </div>
    <div class="col-sm">
      <form action="{{ route('edit', {'id': book.id }) }}" method="POST">
        {{ csrf_field }}
        <div class="form-group">
          <input class="form-control" value="{{ book.title }}" name="title" type="text" placeholder="Add Book title">
        </div>
        <div class="form-group">
          <textarea class="form-control" name="description" type="text"
            placeholder="Description">{{ book.description }}</textarea>
        </div>
        <div class="form-group has-item-centered">
          <!--Add action-->
          <button class="btn btn-outline-success">Add Book</button>
        </div>
      </form>
    </div>
    <div class="col">
    </div>
  </div>
</div>
{% endblock %}

Flash Sessions

We need to send feedback to the user to confirm that books has beed added, deleted or updated. For this purpose we are going to use Flashing Data. Data can be inserted only for the next request. This is useful when using redirection and displaying a success message.

Add Method

#app/http/controllers/StoreController.py
def store(self, request:Request):
        """Add a book and its description"""
        if not request.input('title') and not request.input('description'):
            request.session.flash('warning', 'Empty Title/Description are not accepted!')
        else:
            Book.create(
            title=request.input('title'),
            description=request.input('description')
            )
            request.session.flash('success', 'Book Added!')

        return request.redirect('/')

Delete Method

#app/http/controllers/StoreController.py
def delete(self, request: Request):
        book = Book.find(request.param('id'))
        book.delete()
        request.session.flash('danger', 'Book Deleted!')
        return request.redirect('/')

We will use a Jinja include template to display message.

#resources/templates/helpers/messages.html
{% if session().has('success') %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
    {{ session().get('success') }} <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">&times;</span>
    </button>
</div>
{% elif session().has('warning') %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
    {{ session().get('warning') }} <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">&times;</span>
    </button>
</div>
{% elif session().has('danger') %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
    {{ session().get('danger') }} <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">&times;</span>
    </button>
</div>
{% endif %}

This done, add in store.html this line.

...
</form>     
 {% include '../helpers/messages.html' %}

Now we have all the necessary files and code that we require. Launch craft serve and Voilà. Your BookStore Masonite Application is ready.

Github Repo