DEV Community

Joseph Mancuso for Masonite

Posted on • Updated on

Masonite Framework Tutorial Series Part 2 - Routing

Introduction

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.

Creating our Route

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')
]
Enter fullscreen mode Exit fullscreen mode

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 WelcomeController in 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')
Enter fullscreen mode Exit fullscreen mode

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.

HTTP Methods

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')
]
Enter fullscreen mode Exit fullscreen mode

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.

Middleware

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')
Enter fullscreen mode Exit fullscreen mode

You can specify middleware to be ran in succession by simply passing them as arguments.

Lists

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'),
    ]
]
Enter fullscreen mode Exit fullscreen mode

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'),
        ]
Enter fullscreen mode Exit fullscreen mode

and then import it and use it in this route list:

from somfile import AwesomeRoutes
ROUTES = [
    Get().route('/', 'WelcomeController@show').name('welcome'),
    AwesomeRoutes().routes(),
]
Enter fullscreen mode Exit fullscreen mode

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.

Route Helpers

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')
]
Enter fullscreen mode Exit fullscreen mode

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')
]
Enter fullscreen mode Exit fullscreen mode

Route Groups

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', ...)
]
Enter fullscreen mode Exit fullscreen mode

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'),
    ])
]
Enter fullscreen mode Exit fullscreen mode

Parameters

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', ...),
]
Enter fullscreen mode Exit fullscreen mode

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', ...),
]
Enter fullscreen mode Exit fullscreen mode

This @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 /dashboard/user/joe/edit

We can do the same for an alphanumeric string:

from masonite.helpers.routes import get

ROUTES = [
    get('dashboard/user/@id:string/edit', ...),
]
Enter fullscreen mode Exit fullscreen mode

This will match /dashboard/user/joe/edit but not /dashboard/user/1/edit.

Deeper Module Controller

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
Enter fullscreen mode Exit fullscreen mode

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'),
]
Enter fullscreen mode Exit fullscreen mode

Notice the . 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.

Global Controllers

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
Enter fullscreen mode Exit fullscreen mode

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'),
]
Enter fullscreen mode Exit fullscreen mode

Notice the simple / prefix.

Class Based Controllers

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),
]
Enter fullscreen mode Exit fullscreen mode

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

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.

Activating Subdomains

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()
Enter fullscreen mode Exit fullscreen mode

Once we activate subsdomains, we now have access to using them!

Using Subdomains

We can now simply set the domain for each route but using the domain() method:

from masonite.helpers.routes import get

ROUTES = [
    get('/', 'WelcomeController@show').name('welcome'),
    get('/home', 'SomeController@show').domain('masonite').name('home'),
]
Enter fullscreen mode Exit fullscreen mode

now only the /home route will be available to masonite.website.com and not website.com.

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'),
]
Enter fullscreen mode Exit fullscreen mode

This will catch every subdomain but not anything on website.com.

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'),
]
Enter fullscreen mode Exit fullscreen mode

This will be found on both api.website.com and 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!

Top comments (0)