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);
}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.