mrkeyoor.com_
Sun 20 Sept 07:01 UTC
PyPIWeb Backendupdated 20 Sept 2026

starlette review

Starlette 1.6.0 is an ASGI toolkit for HTTP routes, WebSockets, middleware, streaming responses, application lifespan, background callbacks, sessions, templates, static files, and in-process testing. It gives handlers `Request` objects and expects them to return a `Response`, without adding model validation, dependency injection, database conventions, OpenAPI generation, or a production server. Version 1.6.0 adds `max_body_size` to the application and routing classes so oversized bodies can be stopped at a chosen boundary. It also carries `http.response.debug` information into response extensions, allowing test and diagnostic tooling to inspect debug data from a custom response.

Verdict

Starlette 1.6.0 installed in 0.2 seconds, occupied 2 MB across 4 packages, imported in 0.01 seconds, and produced 0 audit findings in our sandbox. Install it for an explicit ASGI service or framework layer; choose FastAPI or Django when validation and application conventions save more work than direct protocol control.

We installed it

Lab card: what happened when we installed starletteScreenshot of starlette documentation
Install✓ · 0.2s4 packages on disk · 2 MB
Importimport starlette in 0.01s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does starlette install cleanly?

Yes. In a fresh container with an empty cache, pip install starlette finished in 0.2s, leaving 4 packages and 2 MB on disk. pip-audit reported no known vulnerabilities.

What does starlette need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import starlette succeeded in 0.01s, and the package ships py.typed for type checkers.

starlette or fastapi: which should you use?

fastapi: Choose it for typed API inputs, dependency injection, response schemas, and generated OpenAPI on top of ASGI. Starlette 1.6.0 installed in 0.2 seconds, occupied 2 MB across 4 packages, imported in 0.01 seconds, and produced 0 audit findings in our sandbox.

When should you not use starlette?

Choose FastAPI when typed body validation, dependency injection, response models, and generated OpenAPI are product requirements. Starlette leaves all of those out.

API stability4/5Starlette's 1.x line has a clear base of explicit routes, lifespan contexts, request and response classes, middleware, WebSockets, and ASGI composition. Version 1.6.0 adds body limits without changing ordinary handlers. The move to 1.0 removed deprecated route decorators and event lists, so examples written for earlier releases can fail rather than merely warn. Server, AnyIO, multipart, and HTTP client compatibility also needs integration coverage.
Docs4/5The official documentation returned HTTP 200 and has focused chapters for requests, responses, routing, middleware, WebSockets, lifespan, background tasks, thread pools, testing, templates, and static files. It gives exact defaults such as 40 thread tokens and explains the boundary between total body size and multipart limits. It assumes readers already understand ASGI message flow and async cancellation, so custom middleware still demands specification-level reading.
Maintenance5/5GitHub reported 12,571 stars, 60 open issues and pull requests, an unarchived repository, and a push on August 26, 2026. Version 1.6.0 was published on August 8 with application and route body-size limits plus debug response extensions. The repository continues to revise documentation and code after the 1.0 cleanup, and its tracker size is modest relative to the package's position beneath widely used ASGI frameworks.
Ecosystem5/5The recorded registry figure is 159,043,202 weekly downloads, in large part because Starlette sits under FastAPI and other ASGI stacks. Standard ASGI servers, middleware, and mounted applications can share its interface. Optional packages add forms, templates, sessions, and testing as needed. Direct adopters still have to select a server, schema layer, persistence tools, authentication policy, and durable job system themselves.

Discussed on

  1. hnBadHost – CVE-2026-48710: Starlette Host-Header Auth Bypass126 points
  2. hnAsk HN: What are your experiences with using Starlette or FastAPI in production?19 points
  3. hnStarlette 1.0.016 points
  4. hnBadhost and Starlette - most people are dumb13 points
  5. hnStarlette: ASGI (“Async WSGI”) Framework for Python13 points

Use it if

  • A service needs direct ASGI routing and response control, and request schemas or generated OpenAPI would be extra machinery.
  • You are building middleware, protocol endpoints, or a higher-level framework that must compose with other ASGI applications.
  • WebSocket and streamed HTTP handlers should share one lifespan and routing table while the team controls cancellation and cleanup explicitly.
  • FastAPI's underlying request, response, middleware, and test components are useful, but its Pydantic and dependency-injection layers are not.
Skip it if

Setup reality

We installed Starlette 1.6.0 in a fresh Python 3.12 Bookworm sandbox. It completed in 0.2 seconds and left 4 packages occupying 2 MB. Our package check counted 8 direct dependencies, found pure Python with py.typed, and reported the package license as unknown. Python 3.10 or newer is required. pip-audit returned 0 known vulnerabilities, and import starlette worked in 0.01 seconds. That install provides the ASGI application layer, not a network server.

Add Uvicorn, Hypercorn, or another ASGI server and point it at the application object. Optional features add packages: multipart forms need python-multipart, sessions need itsdangerous, and templates need Jinja2. Starlette 1.x applications define Route and WebSocketRoute entries and pass an async lifespan context to the app. TestClient enters lifespan only when used inside a with block, so a plain instance can leave pools or other startup state uninitialized.

Request bodies arrive as ASGI messages. Version 1.6.0 can cap total bytes with max_body_size on the app, router, mount, or route; an inner value overrides an outer one. Multipart parsing has separate max_files, max_fields, and max_part_size controls. The docs say max_part_size limits non-file fields, while uploaded files spool to temporary storage. Middleware runs before route matching, so body-reading middleware must enforce its own limit and replay messages correctly for downstream code.

Synchronous endpoints, file serving, uploads, and sync background callbacks use AnyIO's shared thread pool, whose documented default is 40 tokens. Slow blocking calls can consume that pool and delay unrelated requests. A BackgroundTask starts after the response in the same web process; a crash can lose it, so retryable jobs belong in a queue. For CORS on error responses, the docs recommend wrapping the whole app. Middleware placement decides which redirects and failures receive its headers.

Patterns

Create an explicit HTTP route define-route-table

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

async def health(request):
    return JSONResponse({'ok': True})

app = Starlette(routes=[Route('/health', health)])

Starlette 1.6 uses explicit `Route` objects. Start the app through a separately installed ASGI server such as Uvicorn.

Own a pool through lifespan manage-shared-resource

from contextlib import asynccontextmanager
from starlette.applications import Starlette

@asynccontextmanager
async def lifespan(app):
    async with create_pool() as pool:
        yield {'db': pool}

app = Starlette(routes=routes, lifespan=lifespan)

Starlette waits for lifespan startup before serving requests. Teardown runs after connections close and in-process background tasks finish.

Set app and route body limits cap-request-body

from starlette.applications import Starlette
from starlette.routing import Route

routes = [
    Route('/upload', upload, methods=['POST'], max_body_size=10 * 1024 * 1024),
]
app = Starlette(routes=routes, max_body_size=1024 * 1024)

`max_body_size` arrived in 1.6.0. The 10 MB route value overrides the 1 MB application value for `/upload`.

Bound multipart field counts parse-multipart-form

from starlette.responses import JSONResponse

async def upload(request):
    async with request.form(max_files=2, max_fields=10, max_part_size=1024 * 1024) as form:
        item = form['file']
        return JSONResponse({'name': item.filename})

Install `python-multipart`. The 1 MB `max_part_size` applies to non-file fields; use `max_body_size` to cap the entire upload request.

Serve a WebSocket route echo-websocket-text

from starlette.routing import WebSocketRoute

async def echo(socket):
    await socket.accept()
    async for text in socket.iter_text():
        await socket.send_text(text)

routes = [WebSocketRoute('/ws', echo)]

The text iterator ends on disconnect. Authenticate at the connection boundary and release subscriptions or other resources when the loop exits.

Stream newline-delimited records stream-ndjson

from starlette.responses import StreamingResponse

async def export(request):
    async def rows():
        async for row in read_rows():
            yield row.to_json() + '\n'
    return StreamingResponse(rows(), media_type='application/x-ndjson')

Client disconnects can cancel the generator. Put cursor and file cleanup in `finally` or an async context manager.

Attach brief post-response work run-response-callback

from starlette.background import BackgroundTask
from starlette.responses import JSONResponse

async def create(request):
    record = await save(await request.json())
    task = BackgroundTask(send_notice, record.id)
    return JSONResponse({'id': record.id}, background=task)

`BackgroundTask` runs after response transmission in the web process. It has no durable retry if that process exits.

Apply CORS to error responses wrap-global-cors

from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware

inner = Starlette(routes=routes)
app = CORSMiddleware(
    app=inner,
    allow_origins=['https://app.example'],
    allow_methods=['GET', 'POST'],
)

Wrapping the complete app follows the Starlette docs for adding CORS headers even when outer error handling creates the response.

Run startup and shutdown in tests test-application-lifespan

from starlette.testclient import TestClient

def test_health():
    with TestClient(app) as client:
        response = client.get('/health')
        assert response.json() == {'ok': True}

`TestClient` calls lifespan only as a context manager. A client constructed without `with` skips application startup and shutdown.

Exercise a WebSocket conversation test-websocket

from starlette.testclient import TestClient

def test_echo():
    with TestClient(app) as client:
        with client.websocket_connect('/ws') as socket:
            socket.send_text('hello')
            assert socket.receive_text() == 'hello'

Both `TestClient` and `websocket_connect()` use context managers so lifespan and connection closure occur during the test.

Expose a local asset directory mount-static-directory

from starlette.routing import Mount
from starlette.staticfiles import StaticFiles

routes = [
    Mount('/assets', app=StaticFiles(directory='public'), name='assets'),
]

`StaticFiles` uses the shared thread pool for file work. A reverse proxy or CDN is a better fit for high-volume immutable assets.

Enable cookie-backed sessions set-signed-session

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware

middleware = [Middleware(
    SessionMiddleware,
    secret_key=session_secret,
    https_only=True,
    same_site='lax',
)]
app = Starlette(routes=routes, middleware=middleware)

Install `itsdangerous`. Session data is signed and readable by the client, so do not place secrets in `request.session`; `https_only=True` requires HTTPS.

Alternatives

PackageRegistryPick it when
fastapiPyPIChoose it for typed API inputs, dependency injection, response schemas, and generated OpenAPI on top of ASGI.
DjangoPyPIChoose it when ORM, migrations, authentication, admin, forms, and templates should arrive as one web framework.
sanicPyPIChoose it when an async framework with its own server and a broader application surface is the better operational fit.

More web backend guides

urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.