Life Cycle Hooks#

Life cycle hooks allow the execution of a callable at a certain point during the request-response cycle. The hooks available are:

Name

Runs

before_request

Before the router handler function

after_request

After the route handler function

after_response

After the response has been sent

Before Request#

The before_request hook runs immediately before calling the route handler function. It can be any callable accepting a Request as its first parameter and returns either None or a value that can be used in a response. If a value is returned, the router handler for this request will be bypassed.

from typing import Dict, Optional

from litestar import Litestar, Request, get


async def before_request_handler(request: Request) -> Optional[Dict[str, str]]:
    name = request.query_params["name"]
    if name == "Ben":
        return {"message": "These are not the bytes you are looking for"}
    request.state["message"] = f"Use the handler, {name}"
    return None


@get("/")
async def handler(request: Request, name: str) -> Dict[str, str]:
    message: str = request.state["message"]
    return {"message": message}


app = Litestar(route_handlers=[handler], before_request=before_request_handler)
from litestar import Litestar, Request, get


async def before_request_handler(request: Request) -> dict[str, str] | None:
    name = request.query_params["name"]
    if name == "Ben":
        return {"message": "These are not the bytes you are looking for"}
    request.state["message"] = f"Use the handler, {name}"
    return None


@get("/")
async def handler(request: Request, name: str) -> dict[str, str]:
    message: str = request.state["message"]
    return {"message": message}


app = Litestar(route_handlers=[handler], before_request=before_request_handler)

Run it

> curl http://127.0.0.1:8000/?name=Luke
{"message":"Use the handler, Luke"}
> curl http://127.0.0.1:8000/?name=Ben
{"message":"These are not the bytes you are looking for"}

After Request#

The after_request hook runs after the route handler returned and the response object has been resolved. It can be any callable which takes a Response instance as its first parameter, and returns a Response instance. The Response instance returned does not necessarily have to be the one that was received.

from typing import Dict

from litestar import Litestar, MediaType, Response, get


async def after_request(response: Response) -> Response:
    if response.media_type == MediaType.TEXT:
        return Response({"message": response.content})
    return response


@get("/hello")
async def hello() -> str:
    return "Hello, world"


@get("/goodbye")
async def goodbye() -> Dict[str, str]:
    return {"message": "Goodbye"}


app = Litestar(route_handlers=[hello, goodbye], after_request=after_request)
from litestar import Litestar, MediaType, Response, get


async def after_request(response: Response) -> Response:
    if response.media_type == MediaType.TEXT:
        return Response({"message": response.content})
    return response


@get("/hello")
async def hello() -> str:
    return "Hello, world"


@get("/goodbye")
async def goodbye() -> dict[str, str]:
    return {"message": "Goodbye"}


app = Litestar(route_handlers=[hello, goodbye], after_request=after_request)

Run it

> curl http://127.0.0.1:8000/hello
{"message":"Hello, world"}
> curl http://127.0.0.1:8000/goodbye
{"message":"Goodbye"}

After Response#

The after_response hook runs after the response has been returned by the server. It can be any callable accepting a Request as its first parameter and does not return any value.

This hook is meant for data post-processing, transmission of data to third party services, gathering of metrics, etc.

from collections import defaultdict
from typing import Dict

from litestar import Litestar, Request, get

COUNTER: Dict[str, int] = defaultdict(int)


async def after_response(request: Request) -> None:
    COUNTER[request.url.path] += 1


@get("/hello")
async def hello() -> Dict[str, int]:
    return COUNTER


app = Litestar(route_handlers=[hello], after_response=after_response)
from collections import defaultdict

from litestar import Litestar, Request, get

COUNTER: dict[str, int] = defaultdict(int)


async def after_response(request: Request) -> None:
    COUNTER[request.url.path] += 1


@get("/hello")
async def hello() -> dict[str, int]:
    return COUNTER


app = Litestar(route_handlers=[hello], after_response=after_response)

Run it

> curl http://127.0.0.1:8000/hello
{}
> curl http://127.0.0.1:8000/hello
{"/hello":1}

Note

Since the request has already been returned by the time the after_response is called, the updated state of COUNTER is not reflected in the response.

Layered hooks#

Layered architecture

Life cycle hooks are part of Litestar’s layered architecture, which means you can set them on every layer of the application. If you set hooks on multiple layers, the layer closest to the route handler will take precedence.

You can read more about this here: Layered architecture

from litestar import Litestar, Response, get


def after_request_app(response: Response) -> Response:
    return Response(content=b"app after request")


def after_request_handler(response: Response) -> Response:
    return Response(content=b"handler after request")


@get("/")
async def handler() -> str:
    return "hello, world"


@get("/override", after_request=after_request_handler)
async def handler_with_override() -> str:
    return "hello, world"


app = Litestar(
    route_handlers=[handler, handler_with_override],
    after_request=after_request_app,
)

Run it

> curl http://127.0.0.1:8000/
app after request
> curl http://127.0.0.1:8000/override
handler after request