Skip to content

Request Handling

This guide covers how Django-Bolt processes requests and how to access request data in your handlers.

The request object

Access the full request using a request parameter:

@api.get("/info")
async def request_info(request):
    return {
        "method": request.get("method"),
        "path": request.get("path"),
    }

Request properties

The request dict contains:

Property Type Description
method str HTTP method (GET, POST, etc.)
path str Request path
query dict Query parameters
params dict Path parameters
headers dict Request headers
body bytes Raw request body
context dict Authentication context

Type-safe request

For better IDE support, use the Request type:

from django_bolt import Request

@api.get("/profile", auth=[JWTAuthentication()], guards=[IsAuthenticated()])
async def profile(request: Request):
    # IDE knows about .user, .context, .get(), etc.
    return {"user_id": request.user.id}

The Request type provides:

  • request.user - Authenticated user (lazy loaded)
  • request.context - Authentication context dict
  • request.get(key, default) - Get request property
  • request[key] - Access request property

Path parameters

Extract path parameters using curly braces in the route and matching function arguments:

@api.get("/users/{user_id}/posts/{post_id}")
async def get_post(user_id: int, post_id: int):
    return {"user_id": user_id, "post_id": post_id}

Type conversion happens automatically:

  • int - Converts to integer
  • float - Converts to float
  • str - Keeps as string (default)

Invalid conversions return a 422 Unprocessable Entity.

Query parameters

Parameters without path placeholders become query parameters:

@api.get("/search")
async def search(
    q: str,              # Required
    page: int = 1,       # Optional with default
    limit: int = 20,     # Optional with default
    sort: str | None = None  # Optional, None if not provided
):
    return {"q": q, "page": page, "limit": limit, "sort": sort}

Request body

JSON body

Use msgspec.Struct for validated JSON bodies:

import msgspec

class CreateUser(msgspec.Struct):
    username: str
    email: str
    age: int | None = None

@api.post("/users")
async def create_user(user: CreateUser):
    return {"username": user.username}

Raw body access

Access the raw body bytes:

@api.post("/raw")
async def raw_body(request):
    body = request.get("body", b"")
    return {"size": len(body)}

Headers

Using Annotated

Extract specific headers:

from typing import Annotated
from django_bolt.param_functions import Header

@api.get("/auth")
async def check_auth(
    authorization: Annotated[str, Header(alias="Authorization")]
):
    return {"auth": authorization}

Optional headers

@api.get("/optional-header")
async def optional_header(
    custom: Annotated[str | None, Header(alias="X-Custom")] = None
):
    return {"custom": custom}

All headers

Access all headers from the request:

@api.get("/headers")
async def all_headers(request):
    headers = request.get("headers", {})
    return {"headers": dict(headers)}

Cookies

Extract cookie values:

from typing import Annotated
from django_bolt.param_functions import Cookie

@api.get("/session")
async def get_session(
    session_id: Annotated[str, Cookie(alias="sessionid")]
):
    return {"session_id": session_id}

Form data

URL-encoded forms

from typing import Annotated
from django_bolt.param_functions import Form

@api.post("/login")
async def login(
    username: Annotated[str, Form()],
    password: Annotated[str, Form()]
):
    return {"username": username}

Multipart forms

@api.post("/profile")
async def update_profile(
    name: Annotated[str, Form()],
    bio: Annotated[str, Form()] = ""
):
    return {"name": name, "bio": bio}

File uploads

from typing import Annotated
from django_bolt.param_functions import File

@api.post("/upload")
async def upload(
    files: Annotated[list[dict], File(alias="file")]
):
    for f in files:
        print(f"Received: {f.get('filename')} ({f.get('size')} bytes)")

    return {"uploaded": len(files)}

Each file dict contains:

  • filename - Original filename
  • content_type - MIME type
  • size - Size in bytes
  • content - File bytes

Mixed form and files

@api.post("/submit")
async def submit(
    title: Annotated[str, Form()],
    description: Annotated[str, Form()],
    attachments: Annotated[list[dict], File(alias="file")] = []
):
    return {
        "title": title,
        "attachments": len(attachments)
    }

Dependency injection

Use Depends for reusable parameter extractors:

from django_bolt import Depends

async def get_pagination(page: int = 1, limit: int = 20):
    return {"page": page, "limit": limit, "offset": (page - 1) * limit}

@api.get("/items")
async def list_items(pagination=Depends(get_pagination)):
    return {"pagination": pagination}

Dependencies can be chained and cached. See Dependency Injection for more details.

Validation errors

When request validation fails, Django-Bolt returns a 422 Unprocessable Entity with details:

{
    "detail": [
        {
            "loc": ["body", "email"],
            "msg": "Expected `str` - at `$.email`",
            "type": "validation_error"
        }
    ]
}