Compress plugin
The official @elysia/compress plugin compresses eligible HTTP responses. It negotiates an encoding from the request’s Accept-Encoding header. The runtime determines which codecs are available.
Installation
Section titled “Installation”bun add @elysia/compressBasic use
Section titled “Basic use”Register compress before the routes that use it:
import { Elysia } from 'elysia'import { compress } from '@elysia/compress'
new Elysia() .use(compress()) .get('/', () => 'Hello Elysia! '.repeat(100)) .listen(3000)By default, the plugin uses a 1,024-byte threshold for responses with a known length and buffered responses. The response must pass the media type check. If the client does not accept an available encoding, the plugin returns the response without compression.
Route control
Section titled “Route control”The compress route option belongs in the hook argument before the handler. Disable compression for one route while keeping the default behavior elsewhere:
new Elysia() .use(compress()) .get('/compressed', () => 'Hello Elysia! '.repeat(100)) .get('/plain', { compress: false }, () => 'Hello Elysia! '.repeat(100))Set default to false to require each route to opt in:
new Elysia() .use(compress({ default: false })) .get('/compressed', { compress: true }, () => 'Hello Elysia! '.repeat(100)) .get('/plain', () => 'Hello Elysia! '.repeat(100))An opted-in route still follows the configured media types, threshold, encoding negotiation, and skip conditions.
Options
Section titled “Options”new Elysia().use( compress({ encodings: ['gzip', 'deflate'], threshold: 2048, options: { gzip: { level: 6 } } }))| Option | Default | Description |
|---|---|---|
default | true | Compress routes unless a route sets compress: false. Set it to false to require compress: true. |
encodings | ['br', 'zstd', 'gzip', 'deflate'] | Set the server preference order. The plugin removes codecs that the runtime does not support. Client quality values take priority, and the server order breaks ties. |
threshold | 1024 | Set the minimum number of bytes for responses with a known length and buffered responses. |
types | Built-in predicate | Return true for media types that the plugin may compress. The predicate receives a lowercase media type without parameters. It replaces the built-in predicate. |
stream | true | Compress responses marked with Transfer-Encoding: chunked. Set it to false to leave those responses uncompressed. |
options | {} | Set options for br, zstd, gzip, or deflate. Brotli and Zstandard accept their codec parameters. gzip and deflate accept zlib options such as level. |
The built-in types predicate makes these media types eligible for compression:
text/*application/json- media types that end in
+json
The plugin always excludes text/event-stream.
The threshold applies to a declared Content-Length or a buffered body. A chunked response without a known length can start compression before the plugin knows its final size.
Runtime codec support
Section titled “Runtime codec support”The runtime determines which codecs the plugin can use. The plugin uses Brotli, gzip, and deflate when the runtime provides the matching zlib codec and a Web stream bridge. Zstandard requires its matching zlib codec too. If zlib is unavailable, the Web CompressionStream API can provide gzip and deflate.
The CompressionStream fallback does not apply custom gzip or deflate options. Do not assume that every runtime supports all four encodings.
Skip behavior
Section titled “Skip behavior”The plugin leaves a response uncompressed when any of these conditions apply:
- The request method is
HEAD. - The status is
204,205,206, or304. - The response already has
Content-Encoding. Cache-Controlcontainsno-transform.- The response has no body or uses
text/event-stream. - The
typespredicate rejects the media type. - A known or buffered body is smaller than
threshold. - The response is chunked and
streamisfalse.
Response headers
Section titled “Response headers”A compressed response gets Content-Encoding and includes Accept-Encoding in Vary. The plugin removes Content-Length, Content-Range, Accept-Ranges, and Transfer-Encoding because those values describe the uncompressed response. It also changes a strong ETag to a weak ETag.
If an otherwise eligible response has no accepted encoding, the response remains uncompressed and includes Accept-Encoding in Vary.
Protect responses that contain secrets
Section titled “Protect responses that contain secrets”Compression can expose secrets through a BREACH-style response-size attack when one response contains both a secret and attacker-controlled input. Add no-transform to that response:
app.get('/secret', ({ query, set }) => { set.headers['cache-control'] = 'private, no-transform'
return { echo: query.q, token: session.token }})The plugin detects no-transform and returns the response without compression.