Lifecycle Hooks
Use this skill when the requirement is specifically about the request-handler lifecycle for HTTP handlers, not application bootstrapping or whole-ASGI wrapping.
Execution Workflow
- Decide which stage matches the requirement:
before_request,after_request, orafter_response(the exact lifecycle stage needed for behavior injection). - Choose the layer intentionally: app, router, controller, or handler (the layer to place the hook in).
- Keep hook behavior small, deterministic, and clearly cross-cutting.
- Implement request-state handoff or response mutation explicitly.
- Test normal flow, short-circuit flow, and layer-precedence behavior explicitly.
Core Rules
- Match the hook to the lifecycle stage instead of forcing one hook to do everything.
- Keep hooks transport-focused and cross-cutting; push domain rules into services or handlers.
- Keep
before_requestreturn values deliberate because any non-Noneresponse-compatible value bypasses the route handler. - Keep
after_requestfocused on the resolvedResponse; it may return the same response or a replacement response. - Keep
after_responseside effects safe to run after the response is already sent; it cannot change the response the client received. - Treat hook placement as behavior: Litestar lifecycle hooks are layered, and the closest layer to the handler takes precedence.
- Use request state or other explicit request-scoped storage when
before_requestmust pass data into the handler.
Decision Guide
- Use
before_requestwhen you need request gating, request-state enrichment, or an early HTTP result before the handler runs. - Use
after_requestwhen you need response-object mutation such as reshaping content, adding headers, or swapping the response type. - Use
after_responsewhen you need metrics, counters, audit emission, or third-party notification after the response has already gone out. - Use
litestar-middlewareinstead when the concern needs raw ASGIscope/receive/send, broad wrapping across route groups, built-in middleware, or WebSocket/lifespan coverage. - Use
litestar-app-setupinstead when the concern isbefore_send,after_exception,on_app_init, startup/shutdown hooks, or lifespan-managed resources. - Use
litestar-eventsinstead when one action should fan out into decoupled in-process side effects rather than inline lifecycle interception.
Reference Files
Read only the sections you need:
- For exact hook semantics, short-circuit behavior, handler-to-response boundaries, and layered precedence, read references/request-response-hooks.md.
- For deciding between lifecycle hooks and middleware, including exception-path and ASGI-boundary differences, read references/hook-vs-middleware.md.
Hook Semantics
before_request
- Runs immediately before the route handler function is called.
- Accepts a
Requestas its first parameter. - Returns either
Noneor any value Litestar can use as a response. - Returning a value bypasses the route handler for that request.
- Best for request enrichment, conditional short-circuiting, and request-scoped flags stored on
request.state.
after_request
- Runs after the route handler has returned and Litestar has resolved a
Response. - Accepts a
Responseas its first parameter. - Must return a
Response. - May mutate and return the same response or replace it with a different response object.
- Best for response normalization, response metadata, and response-type transformation after handler execution.
after_response
- Runs after the response has been returned by the server.
- Accepts a
Requestas its first parameter. - Returns
None. - Cannot affect the response that the client already received.
- Best for post-send metrics, counters, audit trails, and external notifications.
Layering Model
- Lifecycle hooks participate in Litestar's layered architecture.
- You can define them on the application, router, controller, or handler layer.
- If the same lifecycle hook is defined on multiple layers, the layer closest to the handler takes precedence.
- Do not assume hook definitions compose across layers; precedence is override-oriented, not additive.
Recommended Defaults
- Prefer one clear responsibility per hook.
- Store transient per-request data on
request.statewhenbefore_requestneeds to communicate with the handler. - Return
Nonefrombefore_requestunless you intentionally want to short-circuit the handler. - Mutate the existing response in
after_requestwhen that is sufficient; replace it only when the response contract truly changes. - Keep
after_responseidempotent enough for retries in tests and safe enough that delayed failures do not corrupt request flow assumptions. - Keep layer overrides narrow and documented by placement rather than comments whenever possible.
Example Pattern
from litestar import Litestar, Request, Response, get
from litestar.status_codes import HTTP_403_FORBIDDEN
async def before_request(request: Request) -> Response | None:
tenant_id = request.headers.get("x-tenant-id")
if tenant_id is None:
return Response({"detail": "missing tenant"}, status_code=HTTP_403_FORBIDDEN)
request.state.tenant_id = tenant_id
return None
async def after_request(response: Response) -> Response:
response.headers["x-service"] = "orders"
return response
async def after_response(request: Request) -> None:
request.app.state.setdefault("paths_seen", []).append(request.url.path)
@get("/orders")
async def list_orders(request: Request) -> dict[str, str]:
return {"tenant_id": request.state.tenant_id}
app = Litestar(
route_handlers=[list_orders],
before_request=before_request,
after_request=after_request,
after_response=after_response,
)Anti-Patterns
- Hiding authoritative business rules in hooks instead of the handler or service layer.
- Returning ad hoc shapes from
before_requestwithout treating them as real response contracts. - Doing heavy network or database work in
after_requestwhen the concern belongs inafter_response, events, or a background mechanism. - Expecting
after_responsemutations to show up in the current client response. - Defining the same hook on multiple layers and expecting all of them to run.
- Using lifecycle hooks as a substitute for startup/shutdown resource management.
- Reaching for middleware when only request or response objects need light interception.
Validation Checklist
- Confirm the chosen hook stage matches the intended timing.
- Confirm
before_requestshort-circuit cases bypass the handler as intended. - Confirm request-scoped data written by
before_requestis available where it is later consumed. - Confirm
after_requestalways returns a validResponse. - Confirm
after_responseside effects tolerate the fact that the response is already gone to the client. - Confirm layer placement is intentional and tested where app/router/controller/handler overrides exist.
- Confirm error-path behavior is tested if hooks must coexist with exception handlers or middleware.
- Confirm the logic still belongs in hooks rather than middleware, events, or app setup.
Cross-Skill Handoffs
- Use
litestar-app-setupfor startup/shutdown hooks, lifespan,before_send,after_exception, andon_app_init. - Use
litestar-middlewarewhen the concern must wrap the ASGI pipeline rather than request and response objects. - Use
litestar-eventsfor decoupled in-process side effects instead of inline hook fanout. - Use
litestar-testingto verify short-circuit behavior, layer precedence, and post-send side effects.