In this series, we will create a basic CRUD Application Tutorial Example from scratch.
We will create a products table with name and detail column using Masonite migration, then we will create routes, controller, view, and model files for the product. We will use bootstrap 5 for design now as a front-end toolkit. So let's follow the below step to create a crud operation with Masonite.
Masonite Installation
mkdir simple-crud
cd simple-crud
Activating Our Virtual Environment
python -m venv venv
source venv/bin/activate
Install Masonite
pip install masonite
Start project
project start .
Start Development Server
python craft serve
This is it! We have the new Masonite Project running now!
Database Configuration
In this step, we will configure mysql DB in our .env
. We need to add database name, mysql username and password.
So we need to create in our mysql server a DB with name simple-crud
.
DB_CONNECTION=mysql
#SQLITE_DB_DATABASE=masonite.sqlite3
DB_HOST=127.0.0.1
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=simple-crud
DB_PORT=3306
DB_LOG=True
Create Migration
Here, we will create products
table using masonite craft migration. So let's use following command to create migration file.
python craft migration create_products_table --create products
After this command you will find one file in the following path database/migrations
and you have to put code below in your migration file for creating the products table.
"""CreateProductsTable Migration."""
from masoniteorm.migrations import Migration
class CreateProductsTable(Migration):
def up(self):
"""
Run the migrations.
"""
with self.schema.create("products") as table:
table.increments("id")
table.string("name")
table.text("details")
table.timestamps()
def down(self):
"""
Revert the migrations.
"""
self.schema.drop("products")
Now we will run this migration by the following command:
python craft migrate
Create Controller and Model
In this step, now we should create new controller as ProductController. So run bellow command and create new controller.
python craft controller ProductController
After the bellow command, you will find a new file in this path app/controllers/ProductController.py
.
In this controller we will create seven methods as bellow methods:
- index
- create
- store
- show
- edit
- update
- delete
app/controllers/ProductController.py
from masonite.request import Request
from masonite.controllers import Controller
from masonite.views import View
from masonite.response import Response
from app.models.Product import Product
from masonite.validation import Validator
class ProductController(Controller):
def index(self, view: View):
products = Product.all()
return view.render('products/index', {'products': products})
def create(self, view: View):
return view.render('products/create')
def store(self, request: Request, response: Response, validate: Validator):
errors = request.validate(
validate.required(['name', 'details']),
)
if errors:
return response.redirect('/products/create').with_errors(errors)
Product.create(
name=request.input('name'),
details=request.input('details')
)
return response.redirect('/products')
def show(self, view: View, request: Request):
product = Product.where('id', request.param('product_id')).first()
return view.render('products/show', {'product': product})
def edit(self, view: View, request: Request):
product = Product.where('id', request.param('product_id')).first()
return view.render('products/edit', {'product': product})
def update(self, request: Request, response: Response, validate: Validator):
errors = request.validate(
validate.required(['name', 'details']),
)
if errors:
return response.redirect('/products/edit/{}'.format(request.input('id'))).with_errors(errors)
product = Product.where('id', request.input('id')).first()
product.name = request.input('name')
product.details = request.input('details')
product.save()
return response.redirect('/products)
def delete(self, request: Request, response: Response):
product = Product.where('id', request.param('product_id')).first()
product.delete()
return response.redirect('/products')
In order to have shared individual input validation errors we need to import and register the following Middleware (ShareErrorsInSessionMiddleware
) in our Kernel.py
.
from masonite.foundation import response_handler
from masonite.storage import StorageCapsule
from masonite.auth import Sign
from masonite.environment import LoadEnvironment
from masonite.utils.structures import load
from masonite.utils.location import base_path
from masonite.middleware import (
SessionMiddleware,
EncryptCookies,
ShareErrorsInSessionMiddleware,
LoadUserMiddleware,
MaintenanceModeMiddleware,
)
from masonite.routes import Route
from masonite.configuration.Configuration import Configuration
from masonite.configuration import config
from app.middlewares import VerifyCsrfToken, AuthenticationMiddleware
class Kernel:
http_middleware = [MaintenanceModeMiddleware, EncryptCookies]
route_middleware = {
"web": [SessionMiddleware, LoadUserMiddleware, VerifyCsrfToken, ShareErrorsInSessionMiddleware],
"auth": [AuthenticationMiddleware],
}
def __init__(self, app):
self.application = app
def register(self):
# Register routes
self.load_environment()
self.register_configurations()
self.register_middleware()
self.register_routes()
self.register_database()
self.register_templates()
self.register_storage()
def load_environment(self):
LoadEnvironment()
def register_configurations(self):
# load configuration
self.application.bind("config.location", "config")
configuration = Configuration(self.application)
configuration.load()
self.application.bind("config", configuration)
key = config("application.key")
self.application.bind("key", key)
self.application.bind("sign", Sign(key))
# set locations
self.application.bind("resources.location", "resources/")
self.application.bind("controllers.location", "app/controllers")
self.application.bind("jobs.location", "app/jobs")
self.application.bind("providers.location", "app/providers")
self.application.bind("mailables.location", "app/mailables")
self.application.bind("listeners.location", "app/listeners")
self.application.bind("validation.location", "app/validation")
self.application.bind("notifications.location", "app/notifications")
self.application.bind("events.location", "app/events")
self.application.bind("tasks.location", "app/tasks")
self.application.bind("models.location", "app/models")
self.application.bind("observers.location", "app/models/observers")
self.application.bind("policies.location", "app/policies")
self.application.bind("commands.location", "app/commands")
self.application.bind("middlewares.location", "app/middlewares")
self.application.bind("server.runner", "masonite.commands.ServeCommand.main")
def register_middleware(self):
self.application.make("middleware").add(self.route_middleware).add(self.http_middleware)
def register_routes(self):
Route.set_controller_locations(self.application.make("controllers.location"))
self.application.bind("routes.location", "routes/web")
self.application.make("router").add(
Route.group(
load(self.application.make("routes.location"), "ROUTES"), middleware=["web"]
)
)
def register_database(self):
from masoniteorm.query import QueryBuilder
self.application.bind(
"builder",
QueryBuilder(connection_details=config("database.databases")),
)
self.application.bind("migrations.location", "databases/migrations")
self.application.bind("seeds.location", "databases/seeds")
self.application.bind("resolver", config("database.db"))
def register_templates(self):
self.application.bind("views.location", "templates/")
def register_storage(self):
storage = StorageCapsule()
storage.add_storage_assets(config("filesystem.staticfiles"))
self.application.bind("storage_capsule", storage)
self.application.set_response_handler(response_handler)
self.application.use_storage_path(base_path("storage"))
It is now the time to create our Product
model.
python craft model Product
app/models/Product.py
""" Product Model """
from masoniteorm.models import Model
class Product(Model):
__fillable__ = ['name', 'details']
Add Routes
We need to add routes for product crud application. So we open our "routes/web.py" file and add following route.
from masonite.routes import Route
ROUTES = [Route.get("/", "ProductController@index"),
Route.get("/products", "ProductController@index"),
Route.get("/products/create", "ProductController@create"),
Route.post("/products", "ProductController@store"),
Route.get("/products/show/@product_id", "ProductController@show"),
Route.get("/products/edit/@product_id", "ProductController@edit"),
Route.post("/products/update", "ProductController@update"),
Route.get('/products/delete/@product_id', 'ProductController@delete'),
]
Add template files
In this step we have to create template files. So mainly we have to create base file and then create new folder "products" then create html files of crud app.
templates/base.html
<!DOCTYPE html>
<html>
<head>
<title>Masonite 4 CRUD Application - blog.popolo.dev</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
</head>
<body>
<div class="container">{% block content %} {% endblock %}</div>
</body>
</html>
templates/products/index.html
{% extends "base.html" %} {% block content %}
<div class="row">
<div class="col-lg-12 d-flex justify-content-between mt-4">
<div class="pull-left">
<h2>Masonite 4 CRUD Example from scratch - blog.popolo.dev</h2>
</div>
<div class="pull-right mb-4">
<a class="btn btn-success" href="products/create"> Create New Product</a>
</div>
</div>
</div>
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Name</th>
<th>Details</th>
<th width="280px">Action</th>
</tr>
@for product in products
<tr>
<td>{{product.id}}</td>
<td>{{product.name}}</td>
<td>{{product.details}}</td>
<td>
<form action="" method="POST">
<a class="btn btn-info" href="/products/show/{{product.id}}">Show</a>
<a class="btn btn-primary" href="/products/edit/{{product.id}}">Edit</a>
<a class="btn btn-danger" href="/products/delete/{{product.id}}">Delete</a>
</form>
</td>
</tr>
@endfor
</table>
{% endblock %}
templates/products/create.html
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-lg-12 mt-4 d-flex justify-content-between">
<div class="pull-left">
<h2>Add New Product</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="/products"> Back</a>
</div>
</div>
</div>
<form action="/products" method="POST">
{{ csrf_field }}
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
</div>
@if errors.has('name')
<div class="text-danger">
<code>{{ errors.get('name')[0] }}</code>
</div>
@endif
</div>
<div class="mb-2 col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="details" placeholder="Detail"></textarea>
</div>
@if errors.has('details')
<div class="text-danger">
<code>{{ errors.get('details')[0] }}</code>
</div>
@endif
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{% endblock %}
templates/products/edit.html
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-lg-12 mt-4 d-flex justify-content-between">
<div class="pull-left">
<h2>Add New Product</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="/products"> Back</a>
</div>
</div>
</div>
<form action="/products/update" method="POST">
{{ csrf_field }}
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<input type="hidden" name="id" value="{{product.id}}" >
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" value="{{product.name}}" class="form-control" placeholder="Name">
</div>
@if errors.has('name')
<div class="text-danger">
<code>{{ errors.get('name')[0] }}</code>
</div>
@endif
</div>
<div class="mb-2 col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="details" placeholder="Detail">{{product.details}}</textarea>
</div>
@if errors.has('details')
<div class="text-danger">
<code>{{ errors.get('details')[0] }}</code>
</div>
@endif
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{% endblock %}
templates/products/show.html
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="mt-4 col-lg-12 mb-2 d-flex justify-content-between">
<div class="pull-left">
<h2> Show Product</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="/products"> Back</a>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
{{ product.name }}
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Details:</strong>
{{ product.details }}
</div>
</div>
</div>
{% endblock %}
Visit application
Now, open your web browser, type the given URL and view the app output:
http://127.0.0.1:8000/products
index page
create page
Edit Page
show page
You can download the code from the repo below: