starlette
Lightweight ASGI framework and toolkit — the foundation underlying FastAPI. starlette features: Router for URL routing, Request/Response primitives, WebSocket support, BackgroundTasks for fire-and-forget, StaticFiles for serving assets, TestClient (synchronous httpx wrapper for testing async apps), Middleware (SessionMiddleware, CORSMiddleware, GZipMiddleware, HTTPSRedirectMiddleware), mount for sub-applications, StreamingResponse for generator-based responses, FileResponse for file downloads, and JSONResponse/HTMLResponse/PlainTextResponse helpers. Used directly when FastAPI overhead is unwanted.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
ASGI framework with no built-in auth. HTTPSRedirectMiddleware enforces HTTPS. TrustedHostMiddleware prevents host header injection. SessionMiddleware uses signed cookies — keep SECRET_KEY secret. CORSMiddleware: configure allow_origins specifically, avoid wildcard in production. StaticFiles: disable directory listing for production.
⚡ Reliability
Best When
Building custom ASGI frameworks, middleware, or lightweight services where FastAPI's overhead is unwanted — starlette is the foundation of FastAPI and provides all ASGI primitives directly.
Avoid When
Full REST API with automatic validation (use FastAPI), traditional WSGI apps (use Flask), or when you need full-stack framework features (use Django).
Use Cases
- • Agent ASGI app — from starlette.applications import Starlette; from starlette.routing import Route; from starlette.responses import JSONResponse; async def homepage(request): return JSONResponse({'status': 'ok'}); app = Starlette(routes=[Route('/', homepage)]) — minimal ASGI; agent creates lightweight ASGI app without FastAPI overhead; run with uvicorn app:app
- • Agent background tasks — from starlette.background import BackgroundTasks; async def send_email(request): tasks = BackgroundTasks(); tasks.add_task(send_notification, email=request.query_params['email']); return JSONResponse({'status': 'queued'}, background=tasks) — fire-and-forget; agent runs tasks after response sent; background= parameter on response triggers after response delivered
- • Agent middleware stack — from starlette.middleware import Middleware; from starlette.middleware.cors import CORSMiddleware; app = Starlette(middleware=[Middleware(CORSMiddleware, allow_origins=['*'], allow_methods=['*'])]) — CORS; agent adds middleware to ASGI app; middleware list applies in order; starlette middlewares work with any ASGI framework
- • Agent WebSocket endpoint — from starlette.websockets import WebSocket; async def ws_endpoint(websocket: WebSocket): await websocket.accept(); data = await websocket.receive_text(); await websocket.send_text(f'Echo: {data}'); await websocket.close() — WebSocket; agent handles WebSocket connections with accept/receive/send/close lifecycle
- • Agent streaming response — from starlette.responses import StreamingResponse; async def stream(request): async def generator(): for chunk in data: yield chunk; return StreamingResponse(generator(), media_type='text/plain') — streaming; agent streams large responses without loading into memory; generator yields bytes or strings; media_type controls Content-Type
Not For
- • Full-featured REST APIs — starlette lacks FastAPI's automatic validation, OpenAPI, and dependency injection; use FastAPI for API development
- • Django-style batteries — no ORM, admin interface, or auth system; starlette is a toolkit not a full framework
- • WSGI apps — starlette is ASGI-only; for WSGI use Flask/Django
Interface
Authentication
No built-in auth — ASGI toolkit. Implement auth via middleware or use authlib/python-jose.
Pricing
starlette is BSD 3-Clause licensed. Created by Tom Christie (encode). Free for all use.
Agent Metadata
Known Gotchas
- ⚠ Request body can only be read once — await request.body() consumes the stream; calling request.body() twice raises error; agent code needing body in middleware AND endpoint: cache body on request.state or read in middleware and pass forward; request.json() also consumes; use starlette's Request.body() pattern with caching
- ⚠ Route ordering matters — Starlette matches routes in order; Route('/items/{id}') before Route('/items/special') means 'special' is never reached; agent code: put specific routes before parameterized routes; or use Mount for sub-application isolation
- ⚠ WebSocket lifecycle: accept before receive — await websocket.accept() must be called before receive/send; skipping accept raises RuntimeError; agent code: always accept() first; WebSocketDisconnect raised on client disconnect — catch it: try: while True: msg = await websocket.receive_text() except WebSocketDisconnect: handle_disconnect()
- ⚠ BackgroundTask exceptions are silently swallowed — tasks.add_task(fn) runs fn after response; if fn raises exception it's logged but not re-raised; agent code: wrap background tasks with try/except and explicit error logging; response returns 200 even if background task fails
- ⚠ Middleware order is reversed from declaration — app = Starlette(middleware=[MiddlewareA, MiddlewareB]); MiddlewareB wraps first (inner), MiddlewareA wraps outer; request passes A→B→endpoint; response passes endpoint→B→A; agent code: put authentication middleware first in list (outermost wrapper)
- ⚠ TestClient requires app as argument not URL — from starlette.testclient import TestClient; client = TestClient(app) — not TestClient('http://localhost'); TestClient runs app in-process via ASGI transport; agent test code: no server needed; client.get('/path') dispatches directly to app
Alternatives
Full Evaluation Report
Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for starlette.
Scores are editorial opinions as of 2026-03-06.