Controllers

HTTP controllers are an intuitive way to organize the route handlers inside dedicated files, rather than using individual functions.

The controllers are stored within the ./app/http/controllers directory, each controller being isolated in a dedicated Python file. You may create a new controller by using the make controller command:

./beam make controller user

A newly created controller is simply a plain Python class to which you can add any number of methods that may be used as route handlers.

user.py
from expanse.view.view import View
from expanse.view.view_factory import ViewFactory


class UserController:

    def index(self, view: ViewFactory) -> View:
        return view.make(
            "users/index", {
                "users": [
                    {"id": 1, "username": "johndoe"},
                    {"id": 2, "username": "janedoe"},
                ]}
        )

Now, we need to register this controller to the router. This can be done in any of your route files:

from expanse.routing.router import Router


def routes(router: Router) -> None:
    from app.http.controllers.user import UserController

    router.get("users", UserController.index)

One important thing to notice here is, even though index() is an instance method, the controller is not instantiated here but, instead, a direct reference to the method is used: this is on purpose and allows Expanse to:

  • Create a new instance of the controller for each request
  • Create this new instance via the service container, allowing you to leverage automatic dependency injection in your controllers.

Dependency injection

Controller classes are created through the service container, which means you can type-hints any dependencies in the controller constructor for them to resolved automatically.

Let's say you manage your users with a UserRepository class, you can inject an instance of it inside the controller by simply type-hinting it:

user.py
from expanse.view.view import View
from expanse.view.view_factory import ViewFactory

from app.repositories.user import UserRepository


class UserController:

    def __init__(self, repository: UserRepository) -> None:
        self._repository = repository

    def index(self, view: ViewFactory) -> View:
        users = self._repository.get_all_users()

        return view.make(
            "users/index", {"users": users}
        )

Method injection

Alternatively — or in addition — to the constructor injection, you can use method injection, just like you would use injection in function route handler:

user.py
from expanse.view.view import View
from expanse.view.view_factory import ViewFactory

from app.repositories.user import UserRepository


class UserController:

    def index(self, repository: UserRepository, view: ViewFactory) -> View:
        users = repository.get_all_users()

        return view.make(
            "users/index", {"users": users}
        )