starlette-compress
ASGI middleware that compresses Starlette and FastAPI responses with ZStandard, Brotli, and GZip, auto-negotiated from Accept-Encoding.
Repository Health
Technical Analysis
starlette-compress is a compression middleware for Starlette (and FastAPI, which is built on it) that picks the best available algorithm — Zstandard, Brotli, or GZip, in that preference order — based on the client’s Accept-Encoding header, falling back to identity (no compression) when nothing matches. It replaces Starlette’s built-in GZipMiddleware with support for more modern codecs and defaults tuned for typical API responses.
The middleware is built around a shared ASGI control loop (CompressionResponder) rather than three separate implementations, so buffered JSON/HTML responses and streaming content like Server-Sent Events go through the same message-classification and header-mutation logic. Buffered responses defer sending http.response.start until the body is known so Content-Length can be recomputed after compression; streaming responses commit the start message immediately (with Content-Encoding set and Content-Length stripped) so clients like EventSource see headers before the first event, and the compressor is flushed after every non-empty body message.
Content-type handling is configurable via add_compress_type/remove_compress_type, with a curated default set of compressible types (JSON, HTML, CSS, JS, SVG, fonts, and more) drawn from nginx and Cloudflare’s compression references. Responses that are too small, already encoded, use range requests, or return a non-compressible content type are skipped entirely, and zerocopysend/pathsend extensions are handled so a raw file send is never advertised as compressed.
What You Get
- Automatic algorithm negotiation across Zstandard, Brotli, and GZip based on the client’s Accept-Encoding header, with identity fallback
- A shared streaming-aware ASGI responder that correctly recomputes Content-Length for buffered responses and defers/streams for SSE and other streaming content types
- Configurable compressed content-type registry via add_compress_type and remove_compress_type, seeded with a curated list of compressible MIME types
- Per-algorithm tuning of compression level/quality (zstd_level, brotli_quality, gzip_level) and a minimum_size threshold to skip compressing tiny responses
- Correct interaction with ASGI pathsend/zerocopysend extensions so file sends are never mis-advertised as compressed
- Optional remove_accept_encoding flag to strip the request header before it reaches downstream middleware or the application
Common Use Cases
- Reducing bandwidth and improving response times for JSON APIs built with FastAPI or Starlette
- Compressing Server-Sent Events streams without breaking early header delivery for EventSource clients
- Replacing Starlette’s built-in GZipMiddleware with broader codec support (Brotli, Zstandard) and better defaults
- Serving compressed static-ish content types (SVG, fonts, CSS/JS) from a Starlette-based app without a separate reverse proxy doing the compression
- Opting specific custom content types like NDJSON/JSONL into compression and/or low-latency streaming behavior
Under The Hood
Architecture The middleware’s core is CompressionResponder in starlette_compress/_responder.py, a single shared ASGI control loop parameterized per algorithm (encoding name, a oneshot compressor, and an encoder factory) and reused by the Zstd, Brotli, and GZip responders. CompressMiddleware in init.py inspects the request’s Accept-Encoding header once, picks the first supported encoding in priority order (zstd, then brotli, then gzip), and delegates the whole request to that responder’s call, falling back to an IdentityResponder when nothing matches. Inside the responder, classify_start_message (in _utils.py) decides per-response whether to skip, buffer, or stream-compress based on status code, existing Content-Encoding, Content-Range, and the response’s content type, and the wrapper closure tracks encoder/start-message state across the ASGI send callback to correctly reorder header mutation before body delivery — buffered responses defer the start message until the terminal body chunk so Content-Length can be corrected, while streaming responses commit the start immediately and flush the encoder per message. What breaks if this abstraction changes: any deviation in message ordering would either send stale Content-Length headers or double-send the start message, which is why the code comments explicitly call out the ordering guarantees (e.g. “fallible oneshot before any header mutation”).
Tech Stack Pure Python targeting 3.10+, with no external framework of its own — it’s an ASGI middleware built directly against Starlette’s ASGIApp/Message/Scope/Send types and MutableHeaders. Compression backends are the brotli/brotlicffi package (selected based on CPython vs alternative implementations), the zstandard package for Python <3.14, and the standard library’s built-in compression.zstd module on 3.14+, plus Python’s built-in gzip/zlib for GZip. The package is part of a larger Rust/Python monorepo built with hatchling and uv, uses ruff for linting and pyright in strict mode for a subset of its core files, and ships CI workflows for cross-version testing and PyPI release automation.
Code Quality
The test suite (tests/test___init__.py) is roughly 2,000 lines covering both asyncio and trio backends via anyio’s backend parametrization, exercising buffered and streaming compression paths, content-type opt-in/opt-out, minimum_size thresholds, Accept-Encoding parsing edge cases (quality values, wildcards), and ASGI extension interactions (pathsend, zerocopysend). Core modules (init.py, _gzip.py, _identity.py, _responder.py, _utils.py) are covered by strict pyright type checking, and the whole package uses __slots__ on its responder classes for memory efficiency. Error handling is explicit about ordering guarantees, with inline comments documenting why certain operations must happen before header mutation to avoid leaving partially-mutated state on failure.
What Makes It Unique Rather than three independent middleware classes, starlette-compress factors compression into one shared control loop parameterized by algorithm, which keeps streaming and buffered response handling consistent across codecs — a design that’s easy to get subtly wrong (header ordering, Content-Length recomputation, SSE flush timing) and where most compression middleware for ASGI frameworks either ignores streaming responses entirely or handles them as an afterthought. Its explicit support for Zstandard (including using the standard library implementation on Python 3.14+) ahead of Brotli and GZip, plus first-class handling of pathsend/zerocopysend extensions, distinguishes it from Starlette’s built-in GZip-only middleware and most third-party alternatives.