Now that we have our installation done and our Masonite application is running, let's work on adding a few routes to our application.
Again we won't be building a specific application in this tutorial. We will only be going through each part of a route in detail and talking us through it, explaining caveats, showing different options etc.
At the end of this series I'll make a video tutorial using these text based tutorials as a transcript. If you don't want to miss that or the next tutorial part then give a follow!
So, a simple route needs 3 basic parts: a request method, a URI and a controller. We will get into controllers in a later tutorial series but let's focus on digging really deep into specifically routes and then we can tie this knowledge in with controllers.
All routes are located in
routes/web.py. All applications have a few different concepts of routes. We might have API routes, HTTP routes, resource routes, internal routes. Because of this, Masonite breaks up all normal front facing HTTP routes and suggests putting it in this file inside a ROUTES constant.
Inside this file we will see some code that looks like:
from masonite.routes import Get, Post ROUTES = [ Get().route('/', 'WelcomeController@show').name('welcome') ]
In it's simplest form this is what you'll be using to create routes. Let's break it down a little more.
We have a
Get() class which tells this specific route that it is a .. you guessed it .. and GET route. This class takes 2 parameters, the URI (starting with a
/) and, in this case, a string that by default points a class called
app.http.controllers.WelcomeController. Not all of your controllers need to in this directory but that's what it is by default. We will talk about how to change this in a later part.
Lastly we have a
name() method which takes a simple string. This string can be anything you want, some people may do things like:
..().name('welcome') ..().name('welcome.home') ..().name('welcome:message')
The syntax if really up to you and what works best for you and your team. It's simply just a string used to identify that route later if we plan to fetch it or redirect to it.
We are not just limited to GET and POST. We can use any request method:
from masonite.routes import Get, Post, Put, Patch, Delete ROUTES = [ Get().route('/', 'WelcomeController@show').name('welcome') Post().route('/create', 'WelcomeController@create').name('welcome.create') Put().route('/update', 'WelcomeController@update').name('welcome.update') Patch().route('/update', 'WelcomeController@update').name('welcome.patch') Delete().route('/delete', 'WelcomeController@delete').name('welcome.delete') ]
If you are building an API endpoint, feel free to use any of these. You may also look into the Masonite Entry package as well if you are interesting in building API's with Masonite.
Like any framework, Masonite has 2 concepts of Middleware. The first concept is Route Middleware and the other is HTTP Middleware. Since this is a route tutorial we will just talk about the Route Middleware but will go into much more detail in a middleware tutorial.
All middleware is defined in
config/middleware.py and can be used in our route above like so:
..().name('welcome').middleware('auth', 'another', 'more')
You can specify middleware to be ran in succession by simply passing them as arguments.
When Masonite loads these routes into the container, it flattens them. This adds support for nesting lists inside of the
ROUTES list like so:
ROUTES = [ Get('/', 'WelcomeController@show').name('welcome'), [ Get.route('/another', 'AnotherController@show').name('another'), ] ]
This isn't very useful at all but what we can do with this is have a class method that returns a list like so:
# inside somefile.py class AwesomeRoutes: def routes(self): return [ Get().route('/another', 'AnotherController@show').name('another'), ]
and then import it and use it in this route list:
from somfile import AwesomeRoutes ROUTES = [ Get().route('/', 'WelcomeController@show').name('welcome'), AwesomeRoutes().routes(), ]
This is very useful for breaking up your routes into multiple files or creating a Masonite package that requires routes to be added to an application.
I personally don't use the classes because I don't like how it looks. I instead use the route helpers that are a simple shorthand to the class based approach and can be used like so:
from masonite.helpers.routes import get, post ROUTES = [ get('/', 'WelcomeController@show').name('welcome') ]
Just a bit of shorthand but I think it's much cleaner. If you like the capitalized class based approach and want a little bit of both we can do something like:
from masonite.helpers.routes import get as Get ROUTES = [ Get('/', 'WelcomeController@show').name('welcome') ]
We can also specify route groups which is simply a list of routes that can be used that can be .. grouped. This is useful if you have a bunch of similar routes and want to make your routes more DRY.
Take this example:
from masonite.helpers.routes import get ROUTES = [ get('/dashboard/user', ...) get('/dashboard/user/edit', ...) get('/dashboard/user/create', ...) get('/dashboard/profile', ...) ]
Notice here that all the routes look very similar. In this instance, we should make a route group:
from masonite.helpers.routes import get, group ROUTES = [ group('/dashboard', [ get('/user', ...), get('/user/edit'), get('/user/create'), get('/user/profile'), ]) ]
This routes feature wouldn't be useful if we weren't able to dynamically fetch parts of the URI. In order to do this we just need to put in an
@ symbol next to the variable name we will want to use later. A great example would be:
from masonite.helpers.routes import get ROUTES = [ get('dashboard/user/@id/edit', ...), ]
Notice here we have an
@id inside our route list. We will now be able to use the
id key to fetch whatever that value is. We will talk about fetching this value in the next tutorial when we talk about controllers.
This will match pretty much anything you throw in there to include digits, letters and alphanumerics. In this case we probably just want to match integers so let's specify that:
from masonite.helpers.routes import get ROUTES = [ get('dashboard/user/@id:int/edit', ...), ]
@id:int will tell Masonite to not match this route if the
id is anything other than an integer. This will match
/dashboard/user/1/edit but not
We can do the same for an alphanumeric string:
from masonite.helpers.routes import get ROUTES = [ get('dashboard/user/@id:string/edit', ...), ]
This will match
/dashboard/user/joe/edit but not
A controller is basically just a class with some methods. If you are coming from Django then Masonite controllers is simply a class wrapped around function based views. Not all controllers are going to be located in
app.http.controllers so you may move them to several places. What most people do is put them into their own categories where you have a structure like this:
app/ http/ controllers/ Dashboard/ SomeController.py Admin/ SomeController.py Leagues/ SomeController.py BaseController.py
This is completely fine and may be the way to go. In this instance we can simply just specify a module deeper in the controller structure:
from masonite.helpers.routes import get ROUTES = [ get(..., 'Dashboard.SomeController'), ]
. notation here. That tells Masonite to check in the
app.http.controller directory but also inside the
Dashboard module instead of the base directory there.
There are two ways to specify controllers globally. If you want to get out of the
app.http.controller directory all together and instead want to place your controllers into a structure like this:
app/ http/ controllers/ BaseController.py controllers Dashboard/ SomeController.py Admin/ SomeController.py Leagues/ SomeController.py
then we can specify a "global" controller which bascially just looks for the controller as if you were importing the module:
from masonite.helpers.routes import get ROUTES = [ get(..., '/controllers.Dashboard.SomeController'), ]
Notice the simple
If you want to get out of the world of string based controllers completely which I sometimes do, especially when I am developing packages, we can simply import the class and specify it without strings:
from masonite.helpers.routes import get from app.http.controllers.WelcomeController import WelcomeController ROUTES = [ get(..., WelcomeController.show), ]
Notice he we didn't instantiate anything. Masonite will find the class, method and module on its own. I personally like this approach a lot better because it is much more explicit. We don't have to worry about the string being able to pick up the controller location implicitly.
Subdomains are extremely useful for any application but we actually turned them off by default. This is because some deployment services like Heroku deploy to things like
meadow-fever-18273.herokuapp.com. This sets a subdomain on the application and you wouldn't be able to show any of your routes until you setup a custom domain name.
In order to activate subdomains, we just need to open any of our Service Provider which has a
wsgi=False class property and we can throw this in the boot method:
def boot(self, Request): Request.activate_subdomains()
Once we activate subsdomains, we now have access to using them!
We can now simply set the domain for each route but using the
from masonite.helpers.routes import get ROUTES = [ get('/', 'WelcomeController@show').name('welcome'), get('/home', 'SomeController@show').domain('masonite').name('home'), ]
now only the
/home route will be available to
masonite.website.com and not
The more common use case is to specify a route on all domains and fetch the domain later in the codebase. For that we can use the asterisk wildcard which will catch all subdomains:
from masonite.helpers.routes import get ROUTES = [ get('/', 'WelcomeController@show').name('welcome'), get('/home', 'SomeController@show').domain('*').name('home'), ]
This will catch every subdomain but not anything on
Lastly we can specify a list of concrete subdomains we wan't to connect to:
from masonite.helpers.routes import get ROUTES = [ get('/', 'WelcomeController@show').name('welcome'), get('/home', 'SomeController@show').domain(['api', 'slack']).name('home'), ]
This will be found on both
slack.website.com but not any other subdomain or main domain. In the next part we will explain how to fetch these values inside a controller.
That's it for routes! Next we'll talk about the awesome world of controllers and how Masonite really shines from other Python web frameworks.
Be sure to give a follow so you don't miss that and be sure to check out Masonite here! See you next time!