Requests#

Request body#

The body of HTTP requests can be accessed using the special data parameter in a handler function.

from typing import Dict

from litestar import Litestar, post


@post(path="/")
async def index(data: Dict[str, str]) -> Dict[str, str]:
    return data


app = Litestar(route_handlers=[index])
from litestar import Litestar, post


@post(path="/")
async def index(data: dict[str, str]) -> dict[str, str]:
    return data


app = Litestar(route_handlers=[index])

The type of data can be any supported type, including

from dataclasses import dataclass

from litestar import Litestar, post


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def index(data: User) -> User:
    return data


app = Litestar(route_handlers=[index])

Validation and customization of OpenAPI documentation#

With the help of Body, you have fine-grained control over the validation of the request body, and can also customize the OpenAPI documentation:

from dataclasses import dataclass

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(title="Create User", description="Create a new user.")],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass

from typing import Annotated

from litestar import Litestar, post
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(title="Create User", description="Create a new user.")],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])

Content-type#

By default, Litestar will try to parse the request body as JSON. While this may be desired in most cases, you might want to specify a different type. You can do so by passing a RequestEncodingType to Body. This will also help to generate the correct media-type in the OpenAPI schema.

URL Encoded Form Data#

To access data sent as url-encoded form data, i.e. application/x-www-form-urlencoded Content-Type header, use Body and specify RequestEncodingType.URL_ENCODED as the media_type:

from dataclasses import dataclass

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(media_type=RequestEncodingType.URL_ENCODED)],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass

from typing import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(media_type=RequestEncodingType.URL_ENCODED)],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])

Note

URL encoded data is inherently less versatile than JSON data - for example, it cannot handle complex dictionaries and deeply nested data. It should only be used for simple data structures.

MultiPart Form Data#

You can access data uploaded using a request with a multipart/form-data Content-Type header by specifying it in the Body function:

from dataclasses import dataclass

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass

from typing import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@dataclass
class User:
    id: int
    name: str


@post(path="/")
async def create_user(
    data: Annotated[User, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> User:
    return data


app = Litestar(route_handlers=[create_user])

File uploads#

In case of files uploaded, Litestar transforms the results into an instance of UploadFile class, which offer a convenient interface for working with files. Therefore, you need to type your file uploads accordingly.

To access a single file simply type data as UploadFile:

from typing_extensions import Annotated

from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", media_type=MediaType.TEXT)
async def handle_file_upload(
    data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
    content = await data.read()
    filename = data.filename
    return f"{filename}, {content.decode()}"


app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated

from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", media_type=MediaType.TEXT)
async def handle_file_upload(
    data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
    content = await data.read()
    filename = data.filename
    return f"{filename}, {content.decode()}"


app = Litestar(route_handlers=[handle_file_upload])
from typing_extensions import Annotated

from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", media_type=MediaType.TEXT, sync_to_thread=False)
def handle_file_upload(
    data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
    content = data.file.read()
    filename = data.filename
    return f"{filename}, {content.decode()}"


app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated

from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", media_type=MediaType.TEXT, sync_to_thread=False)
def handle_file_upload(
    data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
    content = data.file.read()
    filename = data.filename
    return f"{filename}, {content.decode()}"


app = Litestar(route_handlers=[handle_file_upload])

Technical details

UploadFile wraps SpooledTemporaryFile so it can be used asynchronously. Inside a synchronous function we don’t need this wrapper, so we can use its read method directly.

Multiple files#

To access multiple files with known filenames, you can use a pydantic model:

from typing import Any, Dict

from pydantic import BaseConfig, BaseModel
from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


class FormData(BaseModel):
    cv: UploadFile
    diploma: UploadFile

    class Config(BaseConfig):
        arbitrary_types_allowed = True


@post(path="/")
async def handle_file_upload(
    data: Annotated[FormData, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, Any]:
    cv_content = await data.cv.read()
    diploma_content = await data.diploma.read()

    return {"cv": cv_content.decode(), "diploma": diploma_content.decode()}


app = Litestar(route_handlers=[handle_file_upload])
from typing import Any

from pydantic import BaseConfig, BaseModel
from typing import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


class FormData(BaseModel):
    cv: UploadFile
    diploma: UploadFile

    class Config(BaseConfig):
        arbitrary_types_allowed = True


@post(path="/")
async def handle_file_upload(
    data: Annotated[FormData, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, Any]:
    cv_content = await data.cv.read()
    diploma_content = await data.diploma.read()

    return {"cv": cv_content.decode(), "diploma": diploma_content.decode()}


app = Litestar(route_handlers=[handle_file_upload])

Files as a dictionary#

If you do not care about parsing and validation and only want to access the form data as a dictionary, you can use a dict instead:

from typing import Dict

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/")
async def handle_file_upload(
    data: Annotated[Dict[str, UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, str]:
    file_contents = {}
    for name, file in data.items():
        content = await file.read()
        file_contents[name] = content.decode()

    return file_contents


app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/")
async def handle_file_upload(
    data: Annotated[dict[str, UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, str]:
    file_contents = {}
    for name, file in data.items():
        content = await file.read()
        file_contents[name] = content.decode()

    return file_contents


app = Litestar(route_handlers=[handle_file_upload])

Files as a list#

Finally, you can also access the files as a list without the filenames:

from typing import Dict, List

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/")
async def handle_file_upload(
    data: Annotated[List[UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, str]:
    file_contents = {}
    for file in data:
        content = await file.read()
        file_contents[file.filename] = content.decode()

    return file_contents


app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated

from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/")
async def handle_file_upload(
    data: Annotated[list[UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, str]:
    file_contents = {}
    for file in data:
        content = await file.read()
        file_contents[file.filename] = content.decode()

    return file_contents


app = Litestar(route_handlers=[handle_file_upload])

MessagePack data#

To receive MessagePack data, specify the appropriate Content-Type for Body , by using RequestEncodingType.MESSAGEPACK:

msgpack_request.py#
from typing import Any, Dict

from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", sync_to_thread=False)
def msgpack_handler(
    data: Annotated[Dict[str, Any], Body(media_type=RequestEncodingType.MESSAGEPACK)],
) -> Dict[str, Any]:
    # This will try to parse the request body as `MessagePack` regardless of the
    # `Content-Type`
    return data


app = Litestar(route_handlers=[msgpack_handler])
msgpack_request.py#
from typing import Any

from typing import Annotated

from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body


@post(path="/", sync_to_thread=False)
def msgpack_handler(
    data: Annotated[dict[str, Any], Body(media_type=RequestEncodingType.MESSAGEPACK)],
) -> dict[str, Any]:
    # This will try to parse the request body as `MessagePack` regardless of the
    # `Content-Type`
    return data


app = Litestar(route_handlers=[msgpack_handler])

Custom Request#

New in version 2.7.0.

Litestar supports custom request_class instances, which can be used to further configure the default Request. The example below illustrates how to implement custom request class for the whole application.

Example of a custom request at the application level
from litestar import Litestar, Request, get
from litestar.connection.base import empty_receive, empty_send
from litestar.enums import HttpMethod
from litestar.types import Receive, Scope, Send

KITTEN_NAMES_MAP = {
    HttpMethod.GET: "Whiskers",
}


class CustomRequest(Request):
    """Enrich request with the kitten name."""

    __slots__ = ("kitten_name",)

    def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None:
        """Initialize CustomRequest class."""
        super().__init__(scope=scope, receive=receive, send=send)
        self.kitten_name = KITTEN_NAMES_MAP.get(scope["method"], "Mittens")


@get(path="/kitten-name", sync_to_thread=False)
def get_kitten_name(request: CustomRequest) -> str:
    """Get kitten name based on the HTTP method."""
    return request.kitten_name


app = Litestar(
    route_handlers=[get_kitten_name],
    request_class=CustomRequest,
    debug=True,
)

Layered architecture

Request classes are part of Litestar’s layered architecture, which means you can set a request class on every layer of the application. If you have set a request class on multiple layers, the layer closest to the route handler will take precedence.

You can read more about this in the Layered architecture section

Limits#

Body size#

A limit for the allowed request body size can be set on all layers via the request_max_body_size parameter and defaults to 10MB. If a request body exceeds this limit, a 413 - Request Entity Too Large response will be returned. This limit applies to all methods of consuming the request body, including requesting it via the body parameter in a route handler and consuming it through a manually constructed Request instance, e.g. in a middleware.

To disable this limit for a specific handler / router / controller, it can be set to None.

Danger

Setting request_max_body_size=None is strongly discouraged as it exposes the application to a denial of service (DoS) attack by sending arbitrarily large request bodies to the affected endpoint. Because Litestar has to read the whole body to perform certain actions, such as parsing JSON, it will fill up all the available memory / swap until the application / server crashes, should no outside limits be imposed.

This is generally only recommended in environments where the application is running behind a reverse proxy such as NGINX, where a size limit is already set.

Danger

Since request_max_body_size is handled on a per-request basis, it won’t affect middlewares or ASGI handlers when they try to access the request body via the raw ASGI events. To avoid this, middlewares and ASGI handlers should construct a Request instance and use the regular stream() / body() or content-appropriate method to consume the request body in a safe manner.

Tip

For requests that define a Content-Length header, Litestar will not attempt to read the request body should the header value exceed the request_max_body_size.

If the header value is within the allowed bounds, Litestar will verify during the streaming of the request body that it does not exceed the size specified in the header. Should the request exceed this size, it will abort the request with a 400 - Bad Request.