Skip to content

004. Lambda ASGI adapter: Mangum

  • In the context of deploying FastAPI to AWS Lambda behind API Gateway (REST or HTTP API), with those three as invariants,
  • facing the need to bridge API Gateway’s event payload to FastAPI’s ASGI interface,
  • we decided for Mangum — a pure-Python ASGI adapter that wraps the FastAPI app in one line (handler = Mangum(app)) and translates API Gateway v1/v2, ALB, and Function URL events into ASGI scopes in-process,
  • and neglected
    • AWS Lambda Web Adapter (LWA) — runs uvicorn as a real process via a Rust sidecar extension, which gives correct lifespan semantics and streaming support, but streaming requires Lambda Function URL (unavailable when API Gateway is an invariant), and the extension adds 200-500ms cold-start overhead with no offsetting benefit in this deployment model; also container-only,
    • Powertools for AWS Lambda — Event Handler — cannot wrap an existing FastAPI app; requires rewriting routes with Powertools decorators, losing Pydantic model validation, FastAPI dependency injection, and auto-generated OpenAPI docs,
  • to achieve a zero-friction deployment of the existing FastAPI app to Lambda with full ASGI middleware support, minimal cold-start overhead, and no container requirement,
  • accepting
    • no response streaming (full response is buffered before returning),
    • BackgroundTasks run synchronously before the response is sent — not truly in the background,
    • WebSocket routes silently do nothing; Lambda cannot maintain persistent connections.

The Lambda handler is handler = Mangum(app) in the function entrypoint. No structural changes to the FastAPI app are needed. Lifespan events (startup/shutdown) fire once per container initialization — on cold start only — which is the intended behavior for connection-pool setup. ASGI middleware, Pydantic validation, dependency injection, and OpenAPI docs all work as written.

Streaming endpoints must not be added without revisiting this decision; if streaming becomes a requirement, LWA should be re-evaluated alongside a shift from API Gateway to Function URL.

Combined with RAG007 (Haiku-class, ~300 output tokens), the no-streaming constraint shapes the user experience: the SPA shows a loading indicator while the API blocks for the full embedding + retrieval + generation pipeline, then renders the answer all at once. At expected generation latencies (~1–3s), the user sees a blank wait of about that duration. This is acknowledged in specs/ui.md, which lists “Streaming answer display (backend does not support streaming — see RAG004)” as out of scope. The chain Haiku-class → 300 tokens → buffered Mangum response → ~2s wait is the practical UX outcome.

Revisit trigger. If user feedback indicates the blank wait is a problem, the migration path is API Gateway → Lambda Function URL plus AWS Lambda Web Adapter (container-mode), which enables real response streaming. That migration changes the deployment model and adds a small cold-start overhead, so it is not justified without concrete feedback.

Guardrail: do not adopt FastAPI BackgroundTasks

Section titled “Guardrail: do not adopt FastAPI BackgroundTasks”

fastapi.BackgroundTasks runs synchronously before Mangum returns the response. It looks asynchronous in the framework, but in this Lambda + Mangum deployment it adds latency to every request that uses it, with no actual “background” execution. For deferred work (e.g. emitting metrics, writing audit logs after the response), use one of:

  • emit metrics inline via Lambda Powertools (already on the request path, very low overhead — see RAG008)
  • explicit asynchronous boundary: SQS or EventBridge from the handler, processed by a separate Lambda

Treat BackgroundTasks as a deployment-incompatible primitive in this codebase until streaming is enabled.