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.