Ch. 6 · Node.js

Node.js Response Compression with zlib

Compress HTTP responses with gzip or brotli streams, negotiate the encoding, and avoid compressing data that is already compressed.

~2 min readintermediateupdated Oct 5, 2026

Compressing text responses reduces bandwidth and often improves load time, at the cost of CPU. Node’s zlib module provides gzip and brotli as streams, so you can compress while sending instead of buffering the whole response.

Before you start

You should be comfortable with streams and HTTP headers. This article covers response compression; it assumes a server that can set headers and stream a body.

Step-by-step walkthrough

Step 1: Negotiate the encoding

Read the request’s Accept-Encoding header and choose brotli or gzip only if the client supports it, then set Content-Encoding to match. If you compress without advertising it, the client cannot decode the body, so the negotiation and the header must agree.

Step 2: Compress as a stream

Pipe the response body through createGzip() or createBrotliCompress() with pipeline, so bytes leave as they are compressed and memory stays flat. Buffering the entire response to compress it defeats the memory saving for large payloads.

Step 3: Compress the right content types

Compress text formats: HTML, CSS, JavaScript, JSON and SVG. Skip images, video and archives, which are already compressed; recompressing them spends CPU and can grow the payload. Set Vary: Accept-Encoding so caches store the right variant.

Worked scenario

The response body is piped through gzip when the client accepts it.

import { pipeline } from 'node:stream/promises';
import { createGzip } from 'node:zlib';

async function sendCompressed(res, source, acceptsGzip) {
  if (!acceptsGzip) return pipeline(source, res);
  res.setHeader('Content-Encoding', 'gzip');
  res.setHeader('Vary', 'Accept-Encoding');
  return pipeline(source, createGzip(), res);
}
JavaScript

Walk through the example

When the client sends Accept-Encoding: gzip, the body is piped through createGzip and the header tells the client to decode it. Vary prevents a cache from serving a compressed response to a client that did not ask for gzip. When the client does not accept gzip, the body streams uncompressed.

Common mistake

Compressing already-compressed assets, which wastes CPU, and forgetting Content-Length is now unknown, so the response uses chunked encoding. Another is compressing without Vary, letting a shared cache mix variants.

Verify the behavior

Request the same endpoint with and without Accept-Encoding: gzip and confirm the header and body differ correctly. Measure the compressed size and the added CPU time. Confirm that an image response is served without recompression.

Interview exercise

Why not compress JPEG images with gzip?

Answer and reasoning

JPEG data is already entropy-coded and has little redundancy, so gzip finds almost nothing to remove while spending CPU on every request. In some cases the compressed output is slightly larger due to framing overhead. Compress text where redundancy is high; leave already-compressed formats as they are.

Continue learning

Compare stream behavior in Stream backpressure and Pipeline errors. Read the Node.js zlib documentation and try the Node.js interview questions.

More in Node.js

read ✓Node.js · hard

Node.js Clustering Across CPU Cores

Use cluster to run several workers on all cores, restart crashed workers, and understand shared-port and shared-state limits.

~2 min readread →
esc