What’s changed in 2.0?#

This document is an overview of the changes between version 1.51 and 2.0. For a detailed list of all changes, including changes between versions leading up to the 2.0 release, consult the 2.x Changelog.

Starlite → Litestar#

We’re thrilled to introduce some exciting changes in our latest release, version 2! The most noteworthy transformation you will notice is the rebranding of our project, previously known as Starlite, now stepping into the limelight as Litestar.

The name “Starlite” was chosen as an homage to Starlette, the ASGI framework and toolkit Starlite was initially based on. Over the course of its development, Starlite grew more independent and relied less on Starlette, up to the point were Starlette was officially removed as a dependency in November 2022, with the release of v1.39.0.

After careful consideration, it was decided that with the release of 2.0, Starlite would be renamed to Litestar. There were many factors contributing to this decision, but it was mainly driven by concerns from within and outside the community about the possible confusion of the names Starlette and Starlite which - not incidentally - bore a lot of resemblance, which now had outlived its purpose.


Aside from the name, Litestar 2.0 is a direct successor of Starlite 1.x, and the regular release cycle will continue. It was determined that making the first 2.0 release under the new name and continuing with the version scheme from Starlite would cause the least friction. Following that decision, the first release under the new name was v2.0.0alpha3, following the last alpha release of Starlite 2.0, v2.0.0alpha2.

Note

The 1.51 release line is unaffected by this change

Imports#

1.51

2.x

starlite.ASGIConnection

connection.ASGIConnection

starlite.Partial

replaced with DTOs

Enums

starlite.RequestEncodingType

enums.RequestEncodingType

starlite.ScopeType

enums.ScopeType

starlite.OpenAPIMediaType

enums.OpenAPIMediaType

Datastructures

starlite.BackgroundTask

background_tasks.BackgroundTask

starlite.BackgroundTasks

background_tasks.BackgroundTasks

starlite.State

datastructures.State

starlite.ImmutableState

datastructures.ImmutableState

starlite.Cookie

datastructures.Cookie

starlite.FormMultiDict

datastructures.FormMultiDict

starlite.ResponseHeader

datastructures.ResponseHeader

starlite.UploadFile

datastructures.UploadFile

Configuration

starlite.AllowedHostsConfig

config.allowed_hosts.AllowedHostsConfig

starlite.AbstractSecurityConfig

security.AbstractSecurityConfig

starlite.CacheConfig

config.response_cache.ResponseCacheConfig

starlite.CompressionConfig

config.compression.CompressionConfig

starlite.CORSConfig

config.cors.CORSConfig

starlite.CSRFConfig

config.csrf.CSRFConfig

starlite.OpenAPIConfig

openapi.OpenAPIConfig

starlite.StaticFilesConfig

static_files.config.StaticFilesConfig

starlite.TemplateConfig

template.TemplateConfig

starlite.BaseLoggingConfig

logging.config.BaseLoggingConfig

starlite.LoggingConfig

logging.config.LoggingConfig

starlite.StructLoggingConfig

logging.config.StructLoggingConfig

Provide

starlite.datastructures.Provide

di.Provide

Pagination

starlite.AbstractAsyncClassicPaginator

pagination.AbstractAsyncClassicPaginator

starlite.AbstractAsyncCursorPaginator

pagination.AbstractAsyncCursorPaginator

starlite.AbstractAsyncOffsetPaginator

pagination.AbstractAsyncOffsetPaginator

starlite.AbstractSyncClassicPaginator

pagination.AbstractSyncClassicPaginator

starlite.AbstractSyncCursorPaginator

pagination.AbstractSyncCursorPaginator

starlite.AbstractSyncOffsetPaginator

pagination.AbstractSyncOffsetPaginator

starlite.ClassicPagination

pagination.ClassicPagination

starlite.CursorPagination

pagination.CursorPagination

starlite.OffsetPagination

pagination.OffsetPagination

Response Containers

starlite.File

response.File

starlite.Redirect

response.Redirect

starlite.ResponseContainer

response.Response

starlite.Stream

response.Stream

starlite.Template

response.Template

Exceptions

starlite.HTTPException

exceptions.HTTPException

starlite.ImproperlyConfiguredException

exceptions.ImproperlyConfiguredException

starlite.InternalServerException

exceptions.InternalServerException

starlite.MissingDependencyException

exceptions.MissingDependencyException

starlite.NoRouteMatchFoundException

exceptions.NoRouteMatchFoundException

starlite.NotAuthorizedException

exceptions.NotAuthorizedException

starlite.NotFoundException

exceptions.NotFoundException

starlite.PermissionDeniedException

exceptions.PermissionDeniedException

starlite.ServiceUnavailableException

exceptions.ServiceUnavailableException

starlite.StarliteException

exceptions.LitestarException

starlite.TooManyRequestsException

exceptions.TooManyRequestsException

starlite.ValidationException

exceptions.ValidationException

starlite.WebSocketException

exceptions.WebSocketException

Testing

starlite.TestClient

testing.TestClient

starlite.AsyncTestClient

testing.AsyncTestClient

starlite.create_test_client

testing.create_test_client

OpenAPI

starlite.OpenAPIController

openapi.controller.OpenAPIController

starlite.ResponseSpec

openapi.datastructures.ResponseSpec

Middleware

starlite.AbstractAuthenticationMiddleware

middleware.authentication.AbstractAuthenticationMiddleware

starlite.AuthenticationResult

middleware.authentication.AuthenticationResult

starlite.AbstractMiddleware

middleware.AbstractMiddleware

starlite.DefineMiddleware

middleware.DefineMiddleware

starlite.MiddlewareProtocol

middleware.MiddlewareProtocol

Security

starlite.AbstractSecurityConfig

security.AbstractSecurityConfig

Route Handlers

starlite.handlers.asgi

handlers

starlite.handlers.http

handlers

starlite.handlers.websocket

handlers

starlite.ASGIRouteHandler

handlers.ASGIRouteHandler

starlite.BaseRouteHandler

handlers.BaseRouteHandler

starlite.HTTPRouteHandler

handlers.HTTPRouteHandler

starlite.WebsocketRouteHandler

handlers.WebsocketRouteHandler

Routes

starlite.ASGIRoute

routes.ASGIRoute

starlite.BaseRoute

routes.BaseRoute

starlite.HTTPRoute

routes.HTTPRoute

starlite.WebSocketRoute

routes.WebSocketRoute

Parameters

starlite.Body

params.Body

starlite.Parameter

params.Parameter

Response headers#

Response header can now be set using either a Sequence of ResponseHeader, or by using a plain Mapping[str, str]. The typing of ResponseHeader was also changed to be more strict and now only allows string values.

1.51#
from starlite import ResponseHeader, get


@get(response_headers={"my-header": ResponseHeader(value="header-value")})
async def handler() -> str: ...
2.x#
from litestar import ResponseHeader, get


@get(response_headers=[ResponseHeader(name="my-header", value="header-value")])
async def handler() -> str: ...


# or


@get(response_headers={"my-header": "header-value"})
async def handler() -> str: ...

Response cookies#

Response cookies might now also be set using a Mapping[str, str], analogous to Response headers.

@get("/", response_cookies=[Cookie(key="foo", value="bar")])
async def handler() -> None: ...

is equivalent to

@get("/", response_cookies={"foo": "bar"})
async def handler() -> None: ...

SQLAlchemy Plugin#

Support for SQLAlchemy 1 has been dropped and the new plugin will now support SQLAlchemy 2 only.

TODO: Migration instructions

See also

The SQLAlchemy usage documentation and the sqlalchemy API reference

Removal of Pydantic models#

Several Pydantic models used for configuration have been replaced with dataclasses or plain classes. If you relied on implicit data conversion from these models or subclassed them, you might need to adjust your code accordingly.

Plugin protocols#

The plugin protocol has been split into three distinct protocols, covering different use cases:

litestar.plugins.InitPluginProtocol

Hook into an application’s initialization process

litestar.plugins.SerializationPluginProtocol

Extend the serialization and deserialization capabilities of an application

litestar.plugins.OpenAPISchemaPluginProtocol

Extend OpenAPI schema generation

Plugins that made use of all features of the previous API should simply inherit from all three base classes.

Remove 2 argument before_send#

The 2 argument for of before_send hook handlers has been removed. Existing handlers should be changed to include an additional scope parameter.

1.51#
async def before_send(message: Message, state: State) -> None: ...
2.x#
async def before_send(message: Message, state: State, scope: Scope) -> None: ...

See also

Change: Remove support for 2 argument form of before_send and the before_send API reference

initial_state application parameter#

The initial_state argument to Litestar has been replaced with a state keyword argument, accepting an optional State instance.

Existing code using this keyword argument will need to be changed from

1.51#
app = Starlite(..., initial_state={"some": "key"})

to

2.x#
app = Litestar(..., state=State({"some": "key"}))

Stores#

A new module, litestar.stores has been introduced, which replaces the previously used starlite.cache.Cache and server-side session storage backends.

These stores provide a low-level, asynchronous interface for common key/value stores such as Redis and an in-memory implementation. They are currently used for server-side sessions, caching and rate limiting.

Stores are integrated into the Litestar application object via the StoreRegistry, which can be used to register and access stores as well as provide defaults.

from litestar.stores.memory import MemoryStore

store = MemoryStore()


async def main() -> None:
    value = await store.get("key")
    print(value)  # this will print 'None', as no store with this key has been defined yet

    await store.set("key", b"value")
    value = await store.get("key")
    print(value)
Using namespacing#
from litestar import Litestar
from litestar.stores.redis import RedisStore

root_store = RedisStore.with_client()
cache_store = root_store.with_namespace("cache")
session_store = root_store.with_namespace("sessions")


async def before_shutdown() -> None:
    await cache_store.delete_all()


app = Litestar(before_shutdown=[before_shutdown])
Using the registry#
from litestar import Litestar
from litestar.stores.memory import MemoryStore

app = Litestar([], stores={"memory": MemoryStore()})

memory_store = app.stores.get("memory")
# this is the previously defined store

some_other_store = app.stores.get("something_else")
# this will be a newly created instance

assert app.stores.get("something_else") is some_other_store
# but subsequent requests will return the same instance

See also

The Stores usage documentation

Usage of the stores for caching and other integrations#

The newly introduced stores have superseded the removed starlite.cache module in various places.

The following now make use of stores:

The following attributes have been renamed to reduce ambiguity:

In addition, the ASGIConnection.cache property has been removed. It can be replaced by accessing the store directly as described in stores

DTOs#

Data Transfer Objects are now defined using the dto and return_dto arguments to handlers/controllers/routers and the application.

A DTO is any type that inherits from litestar.dto.base_dto.AbstractDTO.

Litestar provides a suite of types that implement the AbstractDTO abstract class and can be used to define DTOs:

For example, to define a DTO from a dataclass:

from dataclasses import dataclass

from litestar import get
from litestar.dto import DTOConfig, DataclassDTO


@dataclass
class MyType:
    some_field: str
    another_field: int


class MyDTO(DataclassDTO[MyType]):
    config = DTOConfig(exclude={"another_field"})


@get(dto=MyDTO)
async def handler() -> MyType:
    return MyType(some_field="some value", another_field=42)
from litestar import post

from .models import User, UserDTO, UserReturnDTO


@post(dto=UserDTO, return_dto=UserReturnDTO)
def create_user(data: User) -> User:
    return data
Renaming fields#
from datetime import datetime

from sqlalchemy.orm import Mapped, mapped_column
from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.dto import DTOConfig, dto_field
from litestar.plugins.sqlalchemy import SQLAlchemyDTO

from .my_lib import Base


class User(Base):
    name: Mapped[str]
    password: Mapped[str] = mapped_column(info=dto_field("private"))
    created_at: Mapped[datetime] = mapped_column(info=dto_field("read-only"))


config = DTOConfig(rename_fields={"name": "userName"})
UserDTO = SQLAlchemyDTO[Annotated[User, config]]


@post("/users", dto=UserDTO, sync_to_thread=False)
def create_user(data: User) -> User:
    assert data.name == "Litestar User"
    data.created_at = datetime.min
    return data


app = Litestar(route_handlers=[create_user])
Renaming fields#
from datetime import datetime

from sqlalchemy.orm import Mapped, mapped_column
from typing import Annotated

from litestar import Litestar, post
from litestar.dto import DTOConfig, dto_field
from litestar.plugins.sqlalchemy import SQLAlchemyDTO

from .my_lib import Base


class User(Base):
    name: Mapped[str]
    password: Mapped[str] = mapped_column(info=dto_field("private"))
    created_at: Mapped[datetime] = mapped_column(info=dto_field("read-only"))


config = DTOConfig(rename_fields={"name": "userName"})
UserDTO = SQLAlchemyDTO[Annotated[User, config]]


@post("/users", dto=UserDTO, sync_to_thread=False)
def create_user(data: User) -> User:
    assert data.name == "Litestar User"
    data.created_at = datetime.min
    return data


app = Litestar(route_handlers=[create_user])

Run it

> curl http://127.0.0.1:8000/users -H Content-Type: application/json -d {"userName":"Litestar User","password":"xyz","created_at":"2023-04-24T00:00:00Z"}
{"created_at":"0001-01-01T00:00:00","id":"a9aeb271-98a8-458e-ba41-317be3f7e519","userName":"Litestar User"}
Excluding fields#
from datetime import datetime
from typing import List
from uuid import UUID

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing_extensions import Annotated

from litestar import Litestar, post
from litestar.dto import DTOConfig, dto_field
from litestar.plugins.sqlalchemy import SQLAlchemyDTO

from .my_lib import Base


class Address(Base):
    street: Mapped[str]
    city: Mapped[str]
    state: Mapped[str]
    zip: Mapped[str]


class Pets(Base):
    name: Mapped[str]
    user_id: Mapped[UUID] = mapped_column(ForeignKey("user.id"))


class User(Base):
    name: Mapped[str]
    password: Mapped[str] = mapped_column(info=dto_field("private"))
    created_at: Mapped[datetime] = mapped_column(info=dto_field("read-only"))
    address_id: Mapped[UUID] = mapped_column(ForeignKey("address.id"), info=dto_field("private"))
    address: Mapped[Address] = relationship(info=dto_field("read-only"))
    pets: Mapped[List[Pets]] = relationship(info=dto_field("read-only"))


UserDTO = SQLAlchemyDTO[User]
config = DTOConfig(
    exclude={
        "id",
        "address.id",
        "address.street",
        "pets.0.id",
        "pets.0.user_id",
    }
)
ReadUserDTO = SQLAlchemyDTO[Annotated[User, config]]


@post("/users", dto=UserDTO, return_dto=ReadUserDTO, sync_to_thread=False)
def create_user(data: User) -> User:
    data.created_at = datetime.min
    data.address = Address(street="123 Main St", city="Anytown", state="NY", zip="12345")
    data.pets = [Pets(id=1, name="Fido"), Pets(id=2, name="Spot")]
    return data


app = Litestar(route_handlers=[create_user])
Excluding fields#
from datetime import datetime
from uuid import UUID

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import Annotated

from litestar import Litestar, post
from litestar.dto import DTOConfig, dto_field
from litestar.plugins.sqlalchemy import SQLAlchemyDTO

from .my_lib import Base


class Address(Base):
    street: Mapped[str]
    city: Mapped[str]
    state: Mapped[str]
    zip: Mapped[str]


class Pets(Base):
    name: Mapped[str]
    user_id: Mapped[UUID] = mapped_column(ForeignKey("user.id"))


class User(Base):
    name: Mapped[str]
    password: Mapped[str] = mapped_column(info=dto_field("private"))
    created_at: Mapped[datetime] = mapped_column(info=dto_field("read-only"))
    address_id: Mapped[UUID] = mapped_column(ForeignKey("address.id"), info=dto_field("private"))
    address: Mapped[Address] = relationship(info=dto_field("read-only"))
    pets: Mapped[list[Pets]] = relationship(info=dto_field("read-only"))


UserDTO = SQLAlchemyDTO[User]
config = DTOConfig(
    exclude={
        "id",
        "address.id",
        "address.street",
        "pets.0.id",
        "pets.0.user_id",
    }
)
ReadUserDTO = SQLAlchemyDTO[Annotated[User, config]]


@post("/users", dto=UserDTO, return_dto=ReadUserDTO, sync_to_thread=False)
def create_user(data: User) -> User:
    data.created_at = datetime.min
    data.address = Address(street="123 Main St", city="Anytown", state="NY", zip="12345")
    data.pets = [Pets(id=1, name="Fido"), Pets(id=2, name="Spot")]
    return data


app = Litestar(route_handlers=[create_user])

Run it

> curl http://127.0.0.1:8000/users -H Content-Type: application/json -d {"name":"Litestar User","password":"xyz","created_at":"2023-04-24T00:00:00Z"}
{"created_at":"0001-01-01T00:00:00","address":{"city":"Anytown","state":"NY","zip":"12345"},"pets":[{"name":"Fido"},{"name":"Spot"}],"name":"Litestar User"}
Marking fields#
from datetime import datetime

from sqlalchemy.orm import Mapped, mapped_column

from litestar import Litestar, post
from litestar.dto import dto_field
from litestar.plugins.sqlalchemy import SQLAlchemyDTO

from .my_lib import Base


class User(Base):
    name: Mapped[str]
    password: Mapped[str] = mapped_column(info=dto_field("private"))
    created_at: Mapped[datetime] = mapped_column(info=dto_field("read-only"))


UserDTO = SQLAlchemyDTO[User]


@post("/users", dto=UserDTO, sync_to_thread=False)
def create_user(data: User) -> User:
    # even though the client sent the password and created_at field, it is not in the data object
    assert "password" not in vars(data)
    assert "created_at" not in vars(data)
    # normally the database would set the created_at timestamp
    data.created_at = datetime.min
    return data  # the response includes the created_at field


app = Litestar(route_handlers=[create_user])

Run it

> curl http://127.0.0.1:8000/users -H Content-Type: application/json -d {"name":"Litestar User","password":"xyz","created_at":"2023-04-24T00:00:00Z"}
{"created_at":"0001-01-01T00:00:00","id":"70e92efe-bde6-4d96-b93c-e268189c2b48","name":"Litestar User"}

See also

The Data Transfer Object (DTO) usage documentation

Application lifespan hooks#

All application lifespan hooks have been merged into on_startup and on_shutdown. The following hooks have been removed:

  • before_startup

  • after_startup

  • before_shutdown

  • after_shutdown

on_startup and on_shutdown now optionally receive the application instance as their first parameter. If your on_startup and on_shutdown hooks made use of the application state, they will now have to access it through the provided application instance.

1.51#
def on_startup(state: State) -> None:
    print(state.something)
2.x#
def on_startup(app: Litestar) -> None:
    print(app.state.something)

Dependencies without Provide#

Dependencies may now be declared without Provide, by passing the callable directly. This can be advantageous in places where the configuration options of Provide are not needed.

async def some_dependency() -> str: ...


app = Litestar(dependencies={"some": Provide(some_dependency)})

is equivalent to

async def some_dependency() -> str: ...


app = Litestar(dependencies={"some": some_dependency})

sync_to_thread#

The sync_to_thread option can be used to run a synchronous callable provided to a route handler or Provide inside a thread pool. Since synchronous functions may block the main thread when not used with sync_to_thread=True, a warning will be raised in these cases. If the synchronous function should not be run in a thread pool, passing sync_to_thread=False will also silence the warning.

Tip

The warning can be disabled entirely by setting the environment variable LITESTAR_WARN_IMPLICIT_SYNC_TO_THREAD=0

1.51#
@get()
def handler() -> None: ...
2.x#
@get(sync_to_thread=False)
def handler() -> None: ...

or

2.x#
@get(sync_to_thread=True)
def handler() -> None: ...

See also

The Sync vs. Async topic guide

HTMX#

Basic support for HTMX requests and responses was added with the litestar.contrib.htmx module.

See also

The HTMX usage documentation

Event bus#

A simple event bus system for Litestar, supporting synchronous and asynchronous listeners and emitters, providing a similar interface to handlers. It currently features a simple in-memory, process-local backend.

See also

The Events usage documentation and the events API reference

Enhanced WebSocket support#

A new set of features for handling WebSockets, including automatic connection handling, (de)serialization of incoming and outgoing data analogous to route handlers, OOP based event dispatching, data iterators and more.

Using a class based listener#
from litestar import Litestar, WebSocket
from litestar.handlers import WebsocketListener


class Handler(WebsocketListener):
    path = "/"

    def on_accept(self, socket: WebSocket) -> None:
        print("Connection accepted")

    def on_disconnect(self, socket: WebSocket) -> None:
        print("Connection closed")

    def on_receive(self, data: str) -> str:
        return data


app = Litestar([Handler])
Echo text#
from litestar import Litestar, websocket_listener


@websocket_listener("/", send_mode="text")
async def handler(data: str) -> str:
    return data


app = Litestar([handler])
Wrapping data in a dataclass#
from dataclasses import dataclass
from datetime import datetime

from litestar import Litestar, websocket_listener


@dataclass
class Message:
    content: str
    timestamp: float


@websocket_listener("/")
async def handler(data: str) -> Message:
    return Message(content=data, timestamp=datetime.now().timestamp())


app = Litestar([handler])
from sqlalchemy.orm import Mapped

from litestar import Litestar, websocket_listener
from litestar.plugins.sqlalchemy import SQLAlchemyDTO, base


class User(base.UUIDBase):
    name: Mapped[str]


UserDTO = SQLAlchemyDTO[User]


@websocket_listener("/", dto=UserDTO)
async def handler(data: User) -> User:
    return data


app = Litestar([handler])
Receiving JSON and sending it back as MessagePack#
from litestar import websocket, WebSocket


@websocket("/")
async def handler(socket: WebSocket) -> None:
    await socket.accept()
    async for message in socket.iter_data(mode):
        await socket.send_msgpack(message)

Attrs signature modelling#

TBD

Annotated support in route handlers#

Annotated can now be used in route handler and dependencies to specify additional information about the fields

@get("/")
def index(param: int = Parameter(gt=5)) -> dict[str, int]: ...
@get("/")
def index(param: Annotated[int, Parameter(gt=5)]) -> dict[str, int]: ...

Channels#

Channels are a general purpose event streaming module, which can for example be used to broadcast messages via WebSockets and includes functionalities such as automatically generating WebSocket route handlers to broadcast messages.

from litestar import Litestar, WebSocket, websocket
from litestar.channels import ChannelsPlugin
from litestar.channels.backends.memory import MemoryChannelsBackend


@websocket("/ws")
async def handler(socket: WebSocket, channels: ChannelsPlugin) -> None:
    await socket.accept()

    async with channels.start_subscription(["some_channel"]) as subscriber, subscriber.run_in_background(
        socket.send_text
    ):
        while True:
            response = await socket.receive_text()
            await socket.send_text(response)


app = Litestar(
    [handler],
    plugins=[ChannelsPlugin(backend=MemoryChannelsBackend(), channels=["some_channel"])],
)

See also

The channels usage documentation

Application lifespan context managers#

A new lifespan argument has been added to Litestar, accepting an asynchronous context manager, wrapping the lifespan of the application. It will be entered with the startup phase and exited on shutdown, providing functionality equal to the on_startup and on_shutdown hooks.

from contextlib import asynccontextmanager
from typing import AsyncGenerator

from sqlalchemy.ext.asyncio import create_async_engine

from litestar import Litestar


@asynccontextmanager
async def db_connection(app: Litestar) -> AsyncGenerator[None, None]:
    engine = getattr(app.state, "engine", None)
    if engine is None:
        engine = create_async_engine("postgresql+asyncpg://postgres:mysecretpassword@pg.db:5432/db")
        app.state.engine = engine

    try:
        yield
    finally:
        await engine.dispose()


app = Litestar(lifespan=[db_connection])
from contextlib import asynccontextmanager
from collections.abc import AsyncGenerator

from sqlalchemy.ext.asyncio import create_async_engine

from litestar import Litestar


@asynccontextmanager
async def db_connection(app: Litestar) -> AsyncGenerator[None, None]:
    engine = getattr(app.state, "engine", None)
    if engine is None:
        engine = create_async_engine("postgresql+asyncpg://postgres:mysecretpassword@pg.db:5432/db")
        app.state.engine = engine

    try:
        yield
    finally:
        await engine.dispose()


app = Litestar(lifespan=[db_connection])

Response types#

Starlite had the concept of Response Containers, which were datatypes used to indicate the type of response returned by a handler. These included File, Redirect, Template and Stream types. These types abstracted the interface of responses from the underlying response itself.

In Litestar, these types still exist, however they are now subclasses of Response and are imported from the litestar.response module. In contrast to Starlite’s Response Containers, these types have more utility for interacting with the outgoing response, such as methods to add headers and cookies. Otherwise, their usage should remain very similar to Starlite.

Litestar also introduces a new layer of ASGI response type, based on ASGIResponse. These types represent the response as an immutable object and are used internally by Litestar to perform the I/O operations of the response. These can be created and returned from handlers, however they are low-level, and lack the utility of the higher-level response types.