typescript 77 lines · 3 tabs

Response compression (only when it helps)

Shared by codesnips Jan 2026
3 tabs
import type { IncomingMessage, ServerResponse } from "http";

const MIN_BYTES = 1024;

const INCOMPRESSIBLE = /^(image|video|audio)\/|application\/(zip|gzip|x-brotli|pdf|octet-stream)/i;

const TEXT_LIKE = /^(text\/|application\/(json|javascript|xml|graphql)|image\/svg)/i;

export function shouldCompress(req: IncomingMessage, res: ServerResponse): boolean {
  const type = String(res.getHeader("Content-Type") || "");

  if (INCOMPRESSIBLE.test(type)) {
    return false;
  }

  const rawLength = res.getHeader("Content-Length");
  if (rawLength === undefined) {
    // Streamed/chunked response: only compress predictable text payloads.
    return TEXT_LIKE.test(type);
  }

  const length = Number(rawLength);
  if (Number.isFinite(length) && length < MIN_BYTES) {
    return false;
  }

  return true;
}
3 files · typescript Explain with highlit

This snippet shows a small compression layer for an Express server that only compresses responses when doing so is actually worthwhile. Naive compression() usage wastes CPU compressing tiny payloads, already-compressed formats like JPEG or MP4, and streamed responses where buffering hurts latency. The three tabs together implement a policy engine, a middleware that applies it, and the wiring that registers it.

In compressionPolicy.ts, shouldCompress centralizes the decision. It reads the Content-Type and Content-Length headers and returns false for anything under MIN_BYTES (1 KB), because the gzip header and CPU cost outweigh the savings on small bodies. It also rejects content types already matching INCOMPRESSIBLE — images, video, audio, and zip archives are entropy-dense and shrink by almost nothing. When Content-Length is absent (a streamed or chunked response) the policy is conservative and allows compression only for known text-like types, since compressing an unbounded stream can add latency for interactive endpoints. This keeps the rule readable and unit-testable in isolation from Express.

In adaptiveCompression.ts, the middleware delegates the buffered path to the standard compression package but supplies a custom filter backed by shouldCompress, so the library still handles content negotiation, Accept-Encoding parsing, and the actual gzip/brotli codecs. The wrapper also honors a per-response opt-out: handlers can set res.locals.noCompression = true (for example on Server-Sent Events) and the filter bails out early. Crucially it respects an existing Content-Encoding header so pre-compressed assets served from disk are never double-encoded, a common corruption bug.

In server.ts, the middleware is registered before the routes with a threshold of 0 because the size gate now lives entirely in the policy, avoiding two competing thresholds. The SSE route demonstrates the escape hatch by flagging res.locals.noCompression before streaming.

The trade-off is explicit: spend CPU only where bytes-on-the-wire genuinely drop. The main pitfall to watch is the BREACH attack surface — compressing responses that mix secrets with attacker-controlled input — so security-sensitive endpoints should opt out via the same noCompression flag.


Related snips

Share this code

Here's the card — post it anywhere.

Response compression (only when it helps) — share card
Link copied