Starlette 1.0 — Skill Reference
Starlette 1.0 shipped March 22, 2026. It is the lightweight ASGI framework behind FastAPI, the Python MCP SDK, and most of the modern async Python web stack. The only hard dependency is anyio.
pip install starlette
pip install uvicorn # ASGI server
pip install starlette[full] # httpx, jinja2, python-multipart, itsdangerous, pyyaml
What changed in 1.0
The 1.0 release removed all previously deprecated APIs and locked in the modern patterns from 0.x. If you're upgrading or referencing old tutorials, these are the breaks:
on_startup / on_shutdown removed. Use lifespan — an async context manager passed to Starlette(lifespan=...). Setup goes before yield, teardown goes after.
- Decorator-style route/middleware registration removed. Pass
routes= and middleware= as explicit lists to the Starlette constructor. No more @app.route(...).
- Old
TemplateResponse(name, context) signature removed. The new signature is TemplateResponse(request, name, context=...) — request is the first positional arg.
SessionMiddleware now tracks access. request.session.is_loaded and request.session.is_modified are available for conditional persistence logic.
- Typed state generic extended to WebSocket.
WebSocket[AppState] gives typed access to websocket.state the same way Request[AppState] does.
Feature index
The full feature reference with code examples lives in references/features.md. It covers 20 sections:
| # |
Section |
What it covers |
| 1 |
Application |
Starlette class, combining routes/middleware/lifespan |
| 2 |
Routing |
Route, Mount, WebSocketRoute, path params, converters |
| 3 |
Requests |
Method, URL, headers, query params, body, form, uploads, streaming |
| 4 |
Responses |
Plain, HTML, JSON, Redirect, Streaming, File, custom |
| 5 |
WebSockets |
Accept, send, receive, close, iteration, typed state |
| 6 |
Endpoints |
Class-based HTTP and WebSocket endpoints |
| 7 |
Lifespan |
Async context manager pattern (replaced startup/shutdown) |
| 8 |
Middleware |
CORS, Sessions, TrustedHost, HTTPS redirect, GZip, BaseHTTPMiddleware, pure ASGI |
| 9 |
Exception Handling |
HTTPException, custom handlers |
| 10 |
Background Tasks |
Attaching work to responses |
| 11 |
Static Files |
StaticFiles, html=True mode |
| 12 |
Templates |
Jinja2, new TemplateResponse signature |
| 13 |
Authentication |
AuthenticationMiddleware, custom backends, requires, scopes |
| 14 |
Configuration |
Config, environ, .env files |
| 15 |
Test Client |
TestClient (httpx-based), async testing |
| 16 |
Schema Generation |
OpenAPI extraction |
| 17 |
Thread Pool |
run_in_threadpool for sync functions |
| 18 |
Data Structures |
State, URL, Headers, QueryParams, MutableHeaders |
| 19 |
ASGI Types |
Raw Scope, Receive, Send |
| 20 |
Complete App |
Full working application tying everything together |
When answering a question about a specific feature, read the corresponding section from references/features.md rather than guessing. The code examples there are verified against the 1.0 release.
Quick patterns
Minimal app
from contextlib import asynccontextmanager
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
@asynccontextmanager
async def lifespan(app):
print("Starting up")
yield
print("Shutting down")
def homepage(request):
return PlainTextResponse("Hello, world!")
app = Starlette(routes=[Route("/", homepage)], lifespan=lifespan)
Route with path parameters
from starlette.routing import Route
from starlette.responses import JSONResponse
async def get_item(request):
item_id = request.path_params["item_id"]
return JSONResponse({"item_id": item_id})
routes = [Route("/items/{item_id:int}", get_item)]
Middleware stack
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
middleware = [
Middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"]),
Middleware(SessionMiddleware, secret_key="your-secret"),
]
app = Starlette(routes=routes, middleware=middleware, lifespan=lifespan)
WebSocket
from starlette.routing import WebSocketRoute
async def ws_echo(websocket):
await websocket.accept()
async for message in websocket.iter_text():
await websocket.send_text(f"Echo: {message}")
routes = [WebSocketRoute("/ws", ws_echo)]
Testing
from starlette.testclient import TestClient
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.text == "Hello, world!"
Common mistakes to catch
- Using
@app.route() or @app.middleware() decorators — removed in 1.0.
- Using
on_startup/on_shutdown lists — removed in 1.0, use lifespan.
- Old
TemplateResponse("index.html", {"request": request}) — first arg is now request.
- Forgetting
await websocket.accept() before sending.
- Passing
debug=True in production — it serves tracebacks to clients.
1---2name: starlette3description: Starlette 1.0 ASGI framework reference with working code examples for every feature. Use this skill whenever writing Starlette applications, building ASGI middleware, working with FastAPI internals, or writing async Python web services. Also trigger when the user mentions Starlette, ASGI, or needs help with routing, WebSockets, middleware, lifespan handlers, or test clients in the Starlette ecosystem — even if they don't name Starlette explicitly but are clearly working with it (e.g., importing from starlette.*, using TestClient with httpx, or writing ASGI scope/ receive/send functions). Covers the 1.0 API exclusively — all deprecated 0.x patterns have been removed.4---56# Starlette 1.0 — Skill Reference78Starlette 1.0 shipped March 22, 2026. It is the lightweight ASGI framework behind FastAPI, the Python MCP SDK, and most of the modern async Python web stack. The only hard dependency is `anyio`.910```shell11pip install starlette12pip install uvicorn # ASGI server13pip install starlette[full] # httpx, jinja2, python-multipart, itsdangerous, pyyaml14```1516## What changed in 1.01718The 1.0 release removed all previously deprecated APIs and locked in the modern patterns from 0.x. If you're upgrading or referencing old tutorials, these are the breaks:19201. **`on_startup` / `on_shutdown` removed.** Use `lifespan` — an async context manager passed to `Starlette(lifespan=...)`. Setup goes before `yield`, teardown goes after.212. **Decorator-style route/middleware registration removed.** Pass `routes=` and `middleware=` as explicit lists to the `Starlette` constructor. No more `@app.route(...)`.223. **Old `TemplateResponse(name, context)` signature removed.** The new signature is `TemplateResponse(request, name, context=...)` — request is the first positional arg.234. **`SessionMiddleware` now tracks access.** `request.session.is_loaded` and `request.session.is_modified` are available for conditional persistence logic.245. **Typed state generic extended to WebSocket.** `WebSocket[AppState]` gives typed access to `websocket.state` the same way `Request[AppState]` does.2526## Feature index2728The full feature reference with code examples lives in `references/features.md`. It covers 20 sections:2930| # | Section | What it covers |31|---|---------|---------------|32| 1 | Application | `Starlette` class, combining routes/middleware/lifespan |33| 2 | Routing | `Route`, `Mount`, `WebSocketRoute`, path params, converters |34| 3 | Requests | Method, URL, headers, query params, body, form, uploads, streaming |35| 4 | Responses | Plain, HTML, JSON, Redirect, Streaming, File, custom |36| 5 | WebSockets | Accept, send, receive, close, iteration, typed state |37| 6 | Endpoints | Class-based HTTP and WebSocket endpoints |38| 7 | Lifespan | Async context manager pattern (replaced startup/shutdown) |39| 8 | Middleware | CORS, Sessions, TrustedHost, HTTPS redirect, GZip, BaseHTTPMiddleware, pure ASGI |40| 9 | Exception Handling | `HTTPException`, custom handlers |41| 10 | Background Tasks | Attaching work to responses |42| 11 | Static Files | `StaticFiles`, `html=True` mode |43| 12 | Templates | Jinja2, new `TemplateResponse` signature |44| 13 | Authentication | `AuthenticationMiddleware`, custom backends, `requires`, scopes |45| 14 | Configuration | `Config`, `environ`, `.env` files |46| 15 | Test Client | `TestClient` (httpx-based), async testing |47| 16 | Schema Generation | OpenAPI extraction |48| 17 | Thread Pool | `run_in_threadpool` for sync functions |49| 18 | Data Structures | `State`, `URL`, `Headers`, `QueryParams`, `MutableHeaders` |50| 19 | ASGI Types | Raw `Scope`, `Receive`, `Send` |51| 20 | Complete App | Full working application tying everything together |5253When answering a question about a specific feature, read the corresponding section from `references/features.md` rather than guessing. The code examples there are verified against the 1.0 release.5455## Quick patterns5657### Minimal app5859```python60from contextlib import asynccontextmanager61from starlette.applications import Starlette62from starlette.responses import PlainTextResponse63from starlette.routing import Route6465@asynccontextmanager66async def lifespan(app):67 print("Starting up")68 yield69 print("Shutting down")7071def homepage(request):72 return PlainTextResponse("Hello, world!")7374app = Starlette(routes=[Route("/", homepage)], lifespan=lifespan)75```7677### Route with path parameters7879```python80from starlette.routing import Route81from starlette.responses import JSONResponse8283async def get_item(request):84 item_id = request.path_params["item_id"]85 return JSONResponse({"item_id": item_id})8687routes = [Route("/items/{item_id:int}", get_item)]88```8990### Middleware stack9192```python93from starlette.middleware import Middleware94from starlette.middleware.cors import CORSMiddleware95from starlette.middleware.sessions import SessionMiddleware9697middleware = [98 Middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"]),99 Middleware(SessionMiddleware, secret_key="your-secret"),100]101102app = Starlette(routes=routes, middleware=middleware, lifespan=lifespan)103```104105### WebSocket106107```python108from starlette.routing import WebSocketRoute109110async def ws_echo(websocket):111 await websocket.accept()112 async for message in websocket.iter_text():113 await websocket.send_text(f"Echo: {message}")114115routes = [WebSocketRoute("/ws", ws_echo)]116```117118### Testing119120```python121from starlette.testclient import TestClient122123client = TestClient(app)124response = client.get("/")125assert response.status_code == 200126assert response.text == "Hello, world!"127```128129## Common mistakes to catch130131- Using `@app.route()` or `@app.middleware()` decorators — removed in 1.0.132- Using `on_startup`/`on_shutdown` lists — removed in 1.0, use `lifespan`.133- Old `TemplateResponse("index.html", {"request": request})` — first arg is now `request`.134- Forgetting `await websocket.accept()` before sending.135- Passing `debug=True` in production — it serves tracebacks to clients.