← blog
JavaScriptAugust 10, 2026 · 4 min read

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

pako is one of npm's most downloaded packages — pulled in by thousands of projects to compress data before storing or sending it. CompressionStream and DecompressionStream do the same job natively, in any modern browser and Node.js 18+, with no dependencies and native C-speed execution.

Parsa Jiravand · Frontend engineer · building bestpractic
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.

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'.

JavaScript
const stream = new CompressionStream('gzip');

DecompressionStream is the mirror:

JavaScript
const stream = new DecompressionStream('gzip');

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

JavaScript
1
2
3
4
5
6
7
8
9
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(); }

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.

A direct swap for the most common pako usage patterns:

JavaScript
1
2
3
4
5
6
7
8
9
// 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

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

Storing large JSON in IndexedDB:

JavaScript
1
2
3
4
5
6
7
8
9
10
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)); }

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:

JavaScript
1
2
3
4
5
// worker.js self.onmessage = async ({ data }) => { const compressed = await compress(JSON.stringify(data.payload)); self.postMessage({ compressed }, [compressed.buffer]); };

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.

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

CompressionStream formatpako equivalentWhen to use
'gzip'pako.gzip / pako.ungzipHTTP transport, file storage — the standard
'deflate'pako.deflate / pako.inflatezlib-wrapped deflate
'deflate-raw'pako.deflateRaw / pako.inflateRawraw 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.

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.

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.

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.

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

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

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:

Originally published on dev.to