Skip to content

Post-Response Background Tasks

When an HTTP route handler triggers background work, you can either enqueue the task inside the handler before returning the response or attach a QueuedBackgroundTask to the Litestar Response.

Runnable Example

docs/examples/learn/background_responses.py
"""Enqueuing tasks after an HTTP response completes using QueuedBackgroundTask."""

from litestar import Litestar, Response, post
from litestar.params import FromPath
from litestar.status_codes import HTTP_201_CREATED
from litestar.testing import AsyncTestClient

from litestar_queues import QueueConfig, QueuedBackgroundTask, QueuePlugin, WorkerConfig, get_task_registry, task


@task("audit.record_login", queue="audit")
async def record_login(username: str) -> str:
    """Persist an audit trail entry for a user login."""
    return f"logged:{username}"


@post("/login/{username:str}")
async def login_user(username: FromPath[str]) -> Response[dict[str, str]]:
    """Return the HTTP response immediately and enqueue the audit task afterward."""
    return Response(
        content={"username": username, "status": "authenticated"},
        background=QueuedBackgroundTask(record_login, username),
    )


async def main() -> None:
    """Verify QueuedBackgroundTask enqueues and executes after the HTTP response."""
    get_task_registry()[record_login.name] = record_login
    app = Litestar(
        route_handlers=[login_user],
        plugins=[
            QueuePlugin(
                config=QueueConfig(
                    queue_backend="memory", execution_backend="immediate", worker=WorkerConfig(placement="external")
                )
            )
        ],
    )
    async with AsyncTestClient(app=app) as client:
        response = await client.post("/login/cody")
        assert response.status_code == HTTP_201_CREATED
        assert response.json() == {"username": "cody", "status": "authenticated"}

Choosing Between In-Handler Enqueue and QueuedBackgroundTask

Dimension In-Handler await queue_service.enqueue(...) Post-Response QueuedBackgroundTask(...)
When Enqueue Runs Inside the route handler before the HTTP response is sent In the ASGI post-response hook after the response body is flushed to the client
Task ID in HTTP Response Yes — handle.id can be returned in the response payload (202 Accepted) No — the response has already been sent before the record is persisted
Enqueue Failure Handling Exceptions propagate to Litestar exception handlers before the client receives 2xx Client already received the response; enqueue errors are logged by the ASGI server
Queue Persistence Full QueuedTaskRecord durability, retries, uniqueness, and worker routing Full QueuedTaskRecord durability, retries, uniqueness, and worker routing
Best For Critical workflows (payments, exports, provisioning) where the caller tracks task_id Non-blocking side effects (audit logs, analytics, cache warming) off the response latency path

Passing Enqueue Options to QueuedBackgroundTask

QueuedBackgroundTask accepts the same scheduling and routing keyword options as QueueService.enqueue:

from datetime import timedelta
from litestar import Response, post
from litestar_queues import QueuedBackgroundTask, task


@task("notifications.send_welcome", queue="notifications")
async def send_welcome(email: str) -> None:
    pass


@post("/signup")
async def signup(email: str) -> Response[dict[str, str]]:
    return Response(
        content={"email": email, "status": "registered"},
        background=QueuedBackgroundTask(
            send_welcome,
            email,
            queue="notifications",
            priority=5,
            run_after=timedelta(seconds=30),
            key=f"welcome:{email}",
        ),
    )

If multiple QueuePlugin instances are mounted with custom QueueNamespace configurations, pass service=queue_service to QueuedBackgroundTask to target a specific QueueService instance.