zlib.createGunzip()

Creates a Transform stream that decompresses gzip data as it flows through.

Signature
zlib.createGunzip([options])
  • Added in Node.js v0.5.8

Parameters

ParameterTypeDescription
$optionsoptionalObjectzlib options: `windowBits`, `chunkSize`, `flush`, `finishFlush`, `dictionary` and `maxOutputLength`, plus any Transform stream option.

Return value

zlib.GunzipA new `zlib.Gunzip` object — a Transform stream whose readable side emits the decompressed data.

Errors

Emits an `'error'` event — not a throw — for invalid gzip data, a truncated stream, or trailing garbage after the last member.

Changelog

  • PHP v5.0.0A truncated input stream now results in an `'error'` event instead of silently ending.
  • PHP v5.9.0Multiple concatenated gzip file members are supported.
  • PHP v6.0.0Trailing garbage at the end of the input stream now results in an `'error'` event.
  • PHP v14.5.0, v12.19.0The `maxOutputLength` option is supported.

zlib.createGunzip() is the streaming inverse of zlib.createGzip(). Write gzip bytes in, read plain bytes out, with memory proportional to the chunk size rather than to the archive. It handles the case that makes streaming essential: a 200 MB .gz that expands to 4 GB can be processed line by line without ever holding the whole thing.

When to reach for it

Any time the compressed data is large or arrives incrementally — reading a compressed log, consuming a gzip HTTP response body, restoring a database dump. For a small buffer already in memory, gunzipSync() is simpler.

What trips people up

  • Errors are events, not exceptions. A try/catch around the pipe does nothing. Corrupt input emits 'error', and with no listener that becomes an uncaught exception which takes the process down. stream.pipeline() handles this for you.
  • Strictness. Since v5.0.0 a truncated stream errors, and since v6.0.0 so does any trailing garbage after the last gzip member. Legitimately concatenated gzip members, however, are decoded as one continuous stream — that is how cat a.gz b.gz works.
  • Bombs. Streaming does not make a decompression bomb safe. Set maxOutputLength, or count bytes as they arrive and destroy the stream past a budget.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Read a huge compressed log line by line without expanding it to disk
  • Decompress a gzip HTTP response body as it downloads
  • Restore a .sql.gz database dump straight into a client
  • Round-trip data through compression in a test without touching the filesystem

Frequently asked questions

How do I read a large gzip file in Node.js without running out of memory?

Pipe fs.createReadStream('file.gz') through zlib.createGunzip() and consume the output incrementally, for example with readline, so memory stays proportional to the chunk size rather than the expanded content.

Why does my gunzip stream crash the process instead of throwing?

Stream failures arrive as 'error' events, so a try/catch around the pipe never fires and an unhandled 'error' becomes an uncaught exception. Use stream.pipeline, which converts the event into a rejected promise or a callback error.

Can createGunzip read several gzip files concatenated together?

Yes — since Node 5.9.0 concatenated gzip members decode as one continuous stream, which is what makes cat a.gz b.gz valid. Non-gzip trailing bytes after the last member still raise an error.

More examples

Read a huge .gz log line by line

Memory stays constant whether the archive holds a thousand lines or a billion, because readline consumes the decompressed stream one line at a time. Both streams get their own error listener — without them, a corrupt archive raises an uncaught exception rather than the message printed here.

Example · javascript
import zlib from 'node:zlib';
import fs from 'node:fs';
import readline from 'node:readline';

const gunzip = zlib.createGunzip();
const source = fs.createReadStream('access.log.gz');

source.on('error', (err) => console.error('read failed:', err.message));
gunzip.on('error', (err) => console.error('bad archive:', err.code));

const rl = readline.createInterface({
  input: source.pipe(gunzip),
  crlfDelay: Infinity
});

let total = 0;
let errors = 0;
for await (const line of rl) {
  total++;
  if (line.includes(' 500 ')) errors++;
}

console.log(`${total} lines scanned, ${errors} server errors`);

Guard a stream against a decompression bomb

The counter runs inside the pipeline, so throwing tears down the gunzip stream immediately rather than after the full expansion. Twenty megabytes of zeroes compress to about twenty kilobytes; without this cap the stream would happily produce all twenty megabytes.

Example · javascript
import zlib from 'node:zlib';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

async function gunzipCapped(compressed, limitBytes) {
  let seen = 0;
  const gunzip = zlib.createGunzip();

  await pipeline(Readable.from([compressed]), gunzip, async function* (stream) {
    for await (const chunk of stream) {
      seen += chunk.length;
      if (seen > limitBytes) throw new Error(`exceeded ${limitBytes} bytes`);
      yield chunk;
    }
  });
  return seen;
}

const bomb = zlib.gzipSync(Buffer.alloc(20 * 1024 * 1024, 0));
console.log('compressed:', bomb.length, 'bytes');

try {
  await gunzipCapped(bomb, 256 * 1024);
} catch (err) {
  console.log('aborted:', err.message);
}

Concatenated members decode as one stream

Three separate gzip members concatenated byte-for-byte decode into one continuous output, which is why appending to a .gz file works and why rotated logs can be joined. Non-gzip bytes after the final member are a different matter: since Node 6 they raise an error instead of being silently dropped.

Example · javascript
import zlib from 'node:zlib';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

// Equivalent to `cat part1.gz part2.gz part3.gz > all.gz`
const joined = Buffer.concat([
  zlib.gzipSync('first member\n'),
  zlib.gzipSync('second member\n'),
  zlib.gzipSync('third member\n')
]);

const out = [];
await pipeline(
  Readable.from([joined]),
  zlib.createGunzip(),
  async function* (s) { for await (const c of s) out.push(c); }
);
console.log(JSON.stringify(Buffer.concat(out).toString()));

// Trailing bytes after the last member are an error, not ignored.
try {
  await pipeline(
    Readable.from([Buffer.concat([joined, Buffer.from('junk')])]),
    zlib.createGunzip(),
    async function* (s) { for await (const c of s) void c; }
  );
} catch (err) {
  console.log('trailing garbage:', err.code);
}

Discussion

  • Be the first to comment on this lesson.