devnotes.

The Zlib Module — Compression

Sending a large file or response over the network costs bandwidth and time proportional to its size — compress it first, and both drop significantly for most kinds of text and structured data. zlib is Node.js's built-in module for exactly that, supporting several compression algorithms including gzip, deflate, and Brotli.

Compressing a string in memory

const zlib = require('zlib');

const original = 'This text will be compressed. '.repeat(50);

zlib.gzip(original, (err, compressed) => {
  if (err) throw err;
  console.log('Original size:', Buffer.byteLength(original));
  console.log('Compressed size:', compressed.length);
});

Repetitive text (like the repeated phrase above) compresses especially well — real-world text, HTML, JSON, and CSS all tend to compress significantly, often shrinking to a fraction of their original size.

Decompressing it back

zlib.gunzip(compressed, (err, decompressed) => {
  if (err) throw err;
  console.log(decompressed.toString()); // back to the original text
});

Compressing a file using streams

For anything larger than a small string, streaming compression avoids loading the whole file into memory at once:

const fs = require('fs');
const zlib = require('zlib');

fs.createReadStream('large-log.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('large-log.txt.gz'));

Decompressing a .gz file back follows the same pattern in reverse:

fs.createReadStream('large-log.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('large-log-restored.txt'));

Compressing HTTP responses — where this matters most in practice

const http = require('http');
const zlib = require('zlib');

http.createServer((req, res) => {
  const acceptEncoding = req.headers['accept-encoding'] || '';
  const payload = JSON.stringify({ message: 'A fairly large JSON response...'.repeat(100) });

  if (acceptEncoding.includes('gzip')) {
    res.writeHead(200, { 'Content-Encoding': 'gzip', 'Content-Type': 'application/json' });
    zlib.gzip(payload, (err, compressed) => res.end(compressed));
  } else {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(payload);
  }
}).listen(3000);

Browsers automatically send an Accept-Encoding header advertising which compression formats they understand, and automatically decompress a response that comes back with a matching Content-Encoding header — the whole exchange is transparent to the person browsing.

A practical note

Most real applications don't hand-roll this logic — a framework like Express commonly handles response compression through a compression middleware package, which wraps exactly this zlib logic behind a single line (app.use(compression())). Knowing what's happening underneath is still useful for understanding why a Content-Encoding: gzip header shows up in your network tab, and for the less common cases where you're compressing something outside of a typical HTTP response — a large file for storage, or data before writing it to a database.

The Zlib Module — Compression | DevNotes