DEV Community

Cover image for You're importing pako to gzip data. `CompressionStream` does it natively.
Parsa Jiravand
Parsa Jiravand

Posted on

You're importing pako to gzip data. `CompressionStream` does it natively.

Compressing data before writing it to IndexedDB or sending it over a slow connection is a real optimization. When a cached API response is too large for reliable storage, or when you need to shrink a JSON payload before POSTing it, pako.gzip() is the standard reach. But pako is a pure-JavaScript port of zlib — written in user space to fill a gap the browser had at the time. The CompressionStream API is that gap closing.

The API

CompressionStream is a transform stream — data goes in one end, compressed data comes out the other. The constructor takes a format string: 'gzip', 'deflate', or 'deflate-raw'.

const stream = new CompressionStream('gzip');
Enter fullscreen mode Exit fullscreen mode

DecompressionStream is the mirror:

const stream = new DecompressionStream('gzip');
Enter fullscreen mode Exit fullscreen mode

The Streams plumbing to feed data in and collect it out is the verbose part. Wrap it once:

async function compress(input, format = 'gzip') {
  const stream = new Blob([input]).stream().pipeThrough(new CompressionStream(format));
  return new Uint8Array(await new Response(stream).arrayBuffer());
}

async function decompress(input, format = 'gzip') {
  const stream = new Blob([input]).stream().pipeThrough(new DecompressionStream(format));
  return new Response(stream).text();
}
Enter fullscreen mode Exit fullscreen mode

Blob.stream().pipeThrough() feeds the data into the transform stream; new Response(stream) collects the result. The idiom is three lines of plumbing written once, then call sites that read plainly.

Replacing pako

A direct swap for the most common pako usage patterns:

// Before — pako
import pako from 'pako';

const compressed = pako.gzip(jsonString);       // Uint8Array
const restored   = pako.ungzip(compressed, { to: 'string' });

// After — native
const compressed = await compress(jsonString);           // Uint8Array
const restored   = await decompress(compressed);         // string
Enter fullscreen mode Exit fullscreen mode

The output is the same gzip-format Uint8Array. Any system that accepts pako's output accepts the native output — the wire format is identical.

Real-world patterns

Storing large JSON in IndexedDB:

async function putCompressed(store, key, data) {
  const compressed = await compress(JSON.stringify(data));
  return store.put(compressed, key);
}

async function getCompressed(store, key) {
  const compressed = await store.get(key);
  if (!compressed) return null;
  return JSON.parse(await decompress(compressed));
}
Enter fullscreen mode Exit fullscreen mode

A 200 KB JSON object typically compresses to 15–30 KB with gzip. For quota-constrained storage like IndexedDB on mobile browsers, that difference matters.

Compressing in a Web Worker:

// worker.js
self.onmessage = async ({ data }) => {
  const compressed = await compress(JSON.stringify(data.payload));
  self.postMessage({ compressed }, [compressed.buffer]);
};
Enter fullscreen mode Exit fullscreen mode

CompressionStream works in Web Workers and Service Workers — the same API, the same two-function wrapper, no restrictions. Offloading compression to a worker keeps the main thread free.

Format reference

The three supported formats map directly to pako's methods:

CompressionStream format pako equivalent When to use
'gzip' pako.gzip / pako.ungzip HTTP transport, file storage — the standard
'deflate' pako.deflate / pako.inflate zlib-wrapped deflate
'deflate-raw' pako.deflateRaw / pako.inflateRaw raw DEFLATE, no wrapper header

'gzip' is the right default for most use cases. Use 'deflate-raw' only when interoperating with a system that expects unwrapped DEFLATE.

Performance

CompressionStream calls into the browser's native zlib implementation — the same C code path used when the browser decompresses HTTP responses with Content-Encoding: gzip. pako re-implements that algorithm in JavaScript. The native path is measurably faster on large inputs:

  • For small payloads (< 10 KB), the difference is negligible.
  • For large payloads (100 KB+), native compression is typically 3–10× faster than pako, because there's no JavaScript overhead and the engine can use SIMD instructions.
  • There's also no bundle cost: pako adds ~45 KB minified to your bundle; CompressionStream adds zero.

Node.js

CompressionStream and DecompressionStream are available in Node.js 18.0 as part of the Web Streams implementation. The two-function wrapper above works unchanged in Node 18+ — useful for isomorphic utilities that run in both browser and server environments without branching on the environment.

Node's older zlib module (zlib.gzipSync, zlib.gunzipSync) is still more ergonomic for Node-only server code. But if you're writing a shared utility that must work in both contexts, CompressionStream is the right choice.

Browser support

CompressionStream is Baseline 2023: Chrome 80 (February 2020), Firefox 113 (May 2023), Safari 16.4 (March 2023), Node.js 18. The API has been in Chromium since early 2020; Firefox and Safari joined in 2023. It's available in all currently-supported browser and runtime versions, including Web Workers and Service Workers.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

🧠 Test yourself

Think it clicked? Take the 9-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

The takeaway

Search your package.json for pako. If it's there and the use case is "compress a string or buffer before storing or sending it," the native API covers you. Replace pako.gzip and pako.ungzip with the two-function wrapper above, remove the dependency, and get native compression speed and zero bundle cost. The Streams API is verbose on its own — the wrapper absorbs that cost once so every call site stays clean.


Thanks for reading! Let's stay connected:

Top comments (0)