Skip to content

Router

The Router is the main object that links the whole Esmerald to the Gateway, WebSocketGateway and handlers.

Router class

The router class is composed by many attributes that are by default populated within the application but Esmerald also allows to add extra custom routers as well but another way is to add a ChildEsmerald app.

from pydantic import BaseModel

from esmerald import Esmerald, Gateway, post


class User(BaseModel):
    name: str
    email: str


@post("/create")
def create(data: User) -> User: ...


app = Esmerald(routes=[Gateway(handler=create)])

The main Router class is instantiated within the Esmerald application with the given routes and the application starts.

Info

When adding another router to the application, custom routers and ChildEsmerald are available as a way of doing it. One is more limited than the other, in this case, custom routers are more limited than ChildEsmerald.

Parameters

All the parameters and defaults are available in the Router Reference.

Warning

The response_class, response_cookies, response_headers, tags and include_in_schema are not used when add_route is used, only when using ChildEsmerald app.

Custom Router

Let's assume there are specific customer submodules inside a customers dedicated file. There are two way of separating the routes within the application, using Include, a ChildEsmerald or by creating another router. Let's focus on the latter.

/application/apps/routers/customers.py
from pydantic import BaseModel

from esmerald import Gateway, JSONResponse, Router, get, post


class Address(BaseModel):
    address_line: str
    street: str
    post_code: str


class Customer(BaseModel):
    name: str
    email: str
    address: Address


@post("/")
def create(data: Customer) -> JSONResponse:
    return JSONResponse({"created": True})


@get("/{customer_id:int}")
async def get_customer(customer_id: int) -> JSONResponse:
    return JSONResponse({"created": True})


router = Router(
    path="/customers",
    routes=[
        Gateway("/", handler=get_customer),
        Gateway("/create", handler=create),
    ],
)

Above you create the /application/apps/routers/customers.py with all the information you need. It does not need to be in one file, you can have a entirely seperate package just to manage the customer, it is up to you.

Now you need to add the new custom router into the main application.

/application/app.py
from apps.routers.customers import router as customers_router

from esmerald import Esmerald

app = Esmerald()
app.add_router(customers_router)

This simple and your router is added to the main Esmerald application.

Child Esmerald Application

What is this? We call it ChildEsmerald but in fact is simply Esmerald but under a different name mostly for visualisation purposes and for the sake of organisation.

Check

Using ChildEsmerald or Esmerald is exactly the same thing, it is only if you prefer to create a sub application and you prefer to use a different class instead of Esmerald to make it more organised.

When organising routes, using the Router class itself can be a bit limiting because there are certain attributes that when used within an instance or a Router to be passed to add_route they will not be picked up.

Example:

  • response_class
  • response_cookies
  • response_headers
  • tags
  • include_in_schema

This is not a limitation or a bug, in fact it is intentional as we want to preserve the integrity of the application.

How does it work

Let's use the same example used in the custom routers with the customers specific routes and rules.

/application/apps/routers/customers.py
from pydantic import BaseModel

from esmerald import ChildEsmerald, Gateway, JSONResponse, get, post


class Address(BaseModel):
    address_line: str
    street: str
    post_code: str


class Customer(BaseModel):
    name: str
    email: str
    address: Address


@post("/")
def create(data: Customer) -> JSONResponse:
    return JSONResponse({"created": True})


@get("/{customer_id:int}")
async def get_customer(customer_id: int) -> JSONResponse:
    return JSONResponse({"created": True})


router = ChildEsmerald(
    routes=[
        Gateway("/", handler=get_customer),
        Gateway("/create", handler=create),
    ],
    include_in_schema=...,
    response_class=...,
    response_headers=...,
    response_cookies=...,
)

Since the ChildEsmerald is a representation of an Esmerald class, we can pass the otherwise limited parameters in the custom router and all the parameters available to Esmerald.

You can add as many ChildEsmerald as you desire, there are no limits.

Now in the main application:

/application/app.py
from apps.routers.customers import router as customers_router

from esmerald import Esmerald, Include

app = Esmerald(routes=[Include("/customers", app=customers_router)])
  • Adding nested applications
/application/app.py
from apps.routers.clients import router as clients_router
from apps.routers.customers import router as customers_router
from apps.routers.restrict import router as restrict_router

from esmerald import Esmerald, Include

app = Esmerald(
    routes=[
        Include("/customers", app=customers_router),
        Include(
            "/api/v1",
            routes=[
                Include("/clients", clients_router),
                Include("/restrict", routes=[Include("/access", restrict_router)]),
            ],
        ),
    ]
)

The example above, it is showing that you could even add the same application within nested includes and for each include you can add specific unique permissions, middlewares, exception handlers and dependencies which are available on each instance of the Include. The options are endeless.

Note

In terms of organisation, ChildEsmerald has a clean approach to the isolation of responsabilities and allow treating every individual module separately and simply adding it in to the main application in the form of Include.

Tip

Treat the ChildEsmerald as an independent Esmerald instance.

Check

When adding a ChildEsmerald or Esmerald application, don't forget to add the unique path to the base Include, this way you can assure the routes are found properly.

Utils

The Router object has some available functionalities that can be useful.

add_route()

from esmerald import Esmerald

app = Esmerald()

app.add_route(
    handler=...,
    dependencies=...,
    exception_handlers=...,
    permissions=...,
    middleware=...,
    name=...,
    interceptors=...,
    include_in_schema=...,
)

Parameters

  • name - Name of the route.
  • include_in_schema - If route should be added to the OpenAPI Schema
  • handler - A HTTP handler.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request. The middlewares of a Include will be checked from top-down.
  • interceptors - A list of interceptors. or Lilya Middleware as they are both converted internally. Read more about Python Protocols.
  • dependencies - A dictionary of string and Inject instances enable application level dependency injection.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.

add_websocket_route()

from esmerald import Esmerald

app = Esmerald()

app.add_websocket_route(
    handler=...,
    dependencies=...,
    exception_handlers=...,
    permissions=...,
    middleware=...,
    interceptors=...,
    name=...,
)

Parameters

  • name - Name of the route.
  • Websocket handler - A websocket handler.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • interceptors - A list of interceptors.
  • middleware - A list of middleware to run for every request. The middlewares of a Include will be checked from top-down. or Lilya Middleware as they are both converted internally. Read more about Python Protocols.
  • dependencies - A dictionary of string and Inject instances enable application level dependency injection.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.

add_child_esmerald()

from esmerald import ChildEsmerald, Esmerald, Gateway, get


@get()
async def home() -> str:
    return "home"


child = ChildEsmerald(routes=[Gateway(handler=home, name="my-apiview")])

app = Esmerald()
app.add_child_esmerald(
    path="/child",
    child=child,
    name=...,
    middleware=...,
    dependencies=...,
    exception_handlers=...,
    interceptors=...,
    permissions=...,
    include_in_schema=...,
    deprecated=...,
    security=...,
)

Parameters

  • path - The path for the child esmerald.
  • child - The ChildEsmerald instance.
  • name - Name of the route.
  • Websocket handler - A websocket handler.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • interceptors - A list of interceptors.
  • middleware - A list of middleware to run for every request. The middlewares of a Include will be checked from top-down. or Lilya Middleware as they are both converted internally. Read more about Python Protocols.
  • dependencies - A dictionary of string and Inject instances enable application level dependency injection.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.
  • include_in_schema - Boolean if this ChildEsmerald should be included in the schema.
  • deprecated - Boolean if this ChildEsmerald should be marked as deprecated.