Skip to content

compress

Negotiates Accept-Encoding and wraps the response stream through the best-matching compressor (Brotli, gzip, or deflate). Compression is skipped automatically for small bodies, non-compressible content types, and no-body status codes.

Pipeline stage: Cross-cutting (place before send)

import {compress} from '@centralping/ergo';
Option Type Default Description
threshold number 1024 Minimum encoded body size in bytes before compression kicks in (Buffer.byteLength for strings using the encoding passed to res.end; .length for Buffers). Only evaluated on the res.end(chunk[, encoding][, callback]) path for single-chunk responses — streaming via res.write() bypasses the threshold check entirely.
encodings string[] ['br', 'gzip', 'deflate'] Supported encodings in priority order. The client’s Accept-Encoding header is negotiated against this list using quality-value weighting per RFC 9110 §12.5.3.

The middleware modifies the response stream in-place (wrapping res.write and res.end). It does not add to the domain accumulator.

None. Compression is silently skipped when conditions are not met.

import {compose, compress} from '@centralping/ergo';
const pipeline = compose(
compress({threshold: 1024, encodings: ['br', 'gzip']}),
(req, res, acc) => ({
response: {body: largePayload},
}),
);
Encoding Algorithm Node.js API
br Brotli zlib.createBrotliCompress()
gzip gzip zlib.createGzip()
deflate DEFLATE zlib.createDeflate()

The encodings option controls which algorithms are offered and their priority order. The client’s Accept-Encoding quality values are negotiated against this list using the negotiator package (RFC 9110 §12.5.3 compliant). The first mutually acceptable encoding wins — identity is excluded since the goal is compression.

The threshold option (default: 1024 bytes) prevents compressing responses that are too small to benefit from compression. The threshold check depends on the response path:

  • res.end(chunk[, encoding][, callback]) path (single-chunk responses from send()): the chunk’s encoded byte length is measured before compression begins (Buffer.byteLength for strings, using the same encoding argument passed to res.end; .length for Buffers). If that size is smaller than threshold, the response is sent uncompressed with the original Content-Length intact.
  • res.write() path (streaming responses): the compressor is activated on the first res.write() call regardless of chunk size. The threshold cannot be checked because the total body size is unknown when streaming begins.

Only compressible content types trigger compression. The middleware tests the Content-Type response header against:

text/*
application/json
application/javascript
application/xml
application/x-www-form-urlencoded
application/*+json (e.g., application/problem+json, application/vnd.api+json)
application/*+xml (e.g., application/hal+xml, application/atom+xml)

The +json and +xml patterns recognize RFC 6838 structured syntax suffixes, ensuring types like application/problem+json (ergo’s error format) and application/vnd.api+json (JSON:API) are correctly compressed.

Binary formats (images, audio, video, application/octet-stream) are not compressible and are always skipped.

When compression activates, the middleware:

  1. Sets Content-Encoding to the negotiated encoding
  2. Removes the Content-Length header — the compressed size is unknown at the time headers are written
  3. The response switches to chunked transfer encoding (HTTP/1.1 default when Content-Length is absent)

When compression is skipped (below threshold, non-compressible type, or no acceptable encoding), Content-Length is preserved as-is.

Compression is skipped entirely when any of the following conditions is true:

  • No Accept-Encoding header — the client did not request compression
  • No matching encoding — the client’s acceptable encodings do not overlap with the configured encodings list
  • Non-compressible Content-Type — the response content type does not match the compressible pattern (binary formats, images, etc.)
  • Status code 204 or 304 — no-body responses have nothing to compress
  • Body below threshold — single-chunk responses smaller than the threshold are sent uncompressed (streaming responses bypass this check)

Verify compression behavior using curl with explicit Accept-Encoding headers:

Terminal window
# Request Brotli compression
curl -H "Accept-Encoding: br" -v http://localhost:3000/api/data
# Request gzip compression
curl -H "Accept-Encoding: gzip" -v http://localhost:3000/api/data
# Verify Content-Encoding in response headers (look for Content-Encoding: br or gzip)
# The response body will be compressed — pipe through a decompressor to inspect:
curl -H "Accept-Encoding: gzip" -s http://localhost:3000/api/data | gunzip
# Request with no compression (omit Accept-Encoding)
curl -v http://localhost:3000/api/data
# → Response includes Content-Length, no Content-Encoding

See the auto-generated compress API docs.