Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

CompressionStream no Node.js: gzip e Brotli

Atualizado em: 22 de outubro de 2026

Rack de servidores processando fluxos de dados no Node.js

O CompressionStream no Node.js permite comprimir dados usando a interface de Web Streams. Em vez de configurar transformações específicas do módulo zlib, a aplicação cria um CompressionStream e conecta o fluxo com pipeThrough(), seguindo o mesmo modelo usado por navegadores modernos.

A API é útil para ferramentas multiplataforma, processamento de respostas, geração de arquivos, integração com fetch e bibliotecas que já trabalham com ReadableStream. Ela também facilita decomprimir conteúdo com DecompressionStream. Mesmo assim, compressão continua envolvendo CPU, memória, limites de saída e riscos como decompression bombs.

Neste guia, você aprenderá a usar gzip, deflate e Brotli quando disponíveis, converter texto e buffers, integrar Web Streams com streams clássicas, aplicar backpressure, comprimir respostas HTTP, testar round trips e escolher quando preferir node:zlib.

Disponibilidade

CompressionStream está disponível globalmente em versões modernas do Node.js e segue o formato da Compression Streams API. Consulte a documentação oficial do Node.js para confirmar estabilidade e formatos suportados pela versão usada.

if (typeof CompressionStream === 'undefined') {
  throw new Error('CompressionStream não está disponível');
}

Versões recentes aceitam formatos tradicionais e podem incluir Brotli. Faça feature detection e testes no runtime real, porque o suporte varia por versão.

Primeiro exemplo com gzip

const input = new TextEncoder().encode('Olá, mundo!');
const source = new Blob([input]).stream();
const compressed = source.pipeThrough(
  new CompressionStream('gzip')
);

const result = new Uint8Array(
  await new Response(compressed).arrayBuffer()
);

console.log(result.byteLength);

O fluxo de entrada passa pelo transformador e a resposta consome o stream comprimido. Para arquivos grandes, não reúna toda a saída em memória; envie ou grave o fluxo diretamente.

Formatos

Os formatos mais comuns são:

  • gzip: amplamente usado em HTTP e arquivos .gz;
  • deflate: formato zlib;
  • deflate-raw: fluxo DEFLATE sem cabeçalho zlib, quando suportado;
  • brotli: compressão eficiente para texto, disponível apenas em versões compatíveis.
const compressor = new CompressionStream('gzip');

Não aceite o nome do formato diretamente de entrada não confiável. Use uma allowlist.

const allowed = new Set(['gzip', 'deflate']);

function createCompressor(format) {
  if (!allowed.has(format)) {
    throw new Error('Formato não permitido');
  }

  return new CompressionStream(format);
}

Decompressão

const decompressed = compressedStream.pipeThrough(
  new DecompressionStream('gzip')
);

const text = await new Response(decompressed).text();

O formato da descompressão deve corresponder ao formato usado na compressão. Um valor incorreto causa erro durante o consumo do stream.

Round trip

async function roundTrip(text) {
  const source = new Blob([text]).stream();

  const compressed = source.pipeThrough(
    new CompressionStream('gzip')
  );

  const decompressed = compressed.pipeThrough(
    new DecompressionStream('gzip')
  );

  return new Response(decompressed).text();
}

console.log(await roundTrip('conteúdo de teste'));

Esse tipo de teste confirma que o pipeline preserva os dados.

Web Streams

CompressionStream implementa a mesma forma de um TransformStream, com propriedades readable e writable. Isso permite escrever manualmente:

const compression = new CompressionStream('gzip');
const writer = compression.writable.getWriter();

await writer.write(new TextEncoder().encode('parte 1'));
await writer.write(new TextEncoder().encode('parte 2'));
await writer.close();

const output = compression.readable;

Na maioria dos casos, pipeThrough() é mais simples e preserva backpressure automaticamente.

Veja Web Streams API no Node.js.

Backpressure

Quando o destino consome lentamente, Web Streams sinalizam ao produtor para reduzir o ritmo. Não ignore as Promises de escrita:

await writer.write(chunk);

Acumular chunks sem aguardar pode elevar o consumo de memória e derrotar o controle de fluxo.

Integrando com fetch

const source = new Blob([JSON.stringify(payload)]).stream();
const body = source.pipeThrough(new CompressionStream('gzip'));

const response = await fetch(endpoint, {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'content-encoding': 'gzip'
  },
  body,
  duplex: 'half'
});

Confirme se o servidor aceita corpo comprimido. O cabeçalho Content-Encoding descreve a transformação aplicada ao conteúdo.

Consulte Fetch no Node.js.

Respostas HTTP comprimidas

Ao servir conteúdo, avalie o cabeçalho Accept-Encoding e responda apenas com formato suportado pelo cliente.

function chooseEncoding(value = '') {
  if (value.includes('br')) return 'br';
  if (value.includes('gzip')) return 'gzip';
  return null;
}

Uma negociação completa deve considerar pesos q, wildcards e exclusões. Para aplicações de produção, use biblioteca ou framework que implemente o protocolo corretamente.

Ao variar por encoding:

res.setHeader('Vary', 'Accept-Encoding');

Sem Vary, caches podem entregar uma resposta Brotli a um cliente que não suporta o formato.

Veja Compressão HTTP no Node.js.

Não comprima tudo

Evite compressão quando:

  • o corpo é muito pequeno;
  • o conteúdo já está comprimido, como JPEG, PNG, ZIP ou MP4;
  • a CPU é o gargalo;
  • a resposta é extremamente sensível à latência inicial;
  • a compressão aumenta o tamanho;
  • o cliente não anunciou suporte.

Defina um tamanho mínimo e uma lista de MIME types compressíveis.

function shouldCompress({ contentType, size }) {
  if (size < 1024) return false;

  return contentType.startsWith('text/') ||
    contentType.includes('json') ||
    contentType.includes('javascript');
}

Streaming de arquivo

Para arquivos grandes, converta uma stream clássica para Web Stream:

import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';

const file = createReadStream('report.json');
const webStream = Readable.toWeb(file);

const compressed = webStream.pipeThrough(
  new CompressionStream('gzip')
);

Para voltar ao modelo clássico:

const nodeStream = Readable.fromWeb(compressed);

Isso permite conectar a arquivos, sockets e APIs tradicionais.

Consulte Streams no Node.js.

Gravando um arquivo gzip

import { createReadStream, createWriteStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

const source = Readable.toWeb(
  createReadStream('data.ndjson')
);

const compressed = source.pipeThrough(
  new CompressionStream('gzip')
);

await pipeline(
  Readable.fromWeb(compressed),
  createWriteStream('data.ndjson.gz')
);

pipeline() propaga erros e fecha recursos corretamente.

Brotli

Brotli costuma produzir arquivos menores para texto, mas pode consumir mais CPU. Verifique suporte:

function supportsBrotli() {
  try {
    new CompressionStream('brotli');
    return true;
  } catch {
    return false;
  }
}

Meça tempo de compressão, tamanho final e efeito na latência. Para conteúdo estático, comprimir durante o build pode ser melhor que comprimir em cada requisição.

CPU-bound

Compressão consome CPU. Em alto volume, ela pode bloquear trabalho útil ou aumentar o event loop lag. Considere:

  • limitar concorrência;
  • pré-comprimir assets;
  • usar CDN ou proxy;
  • mover tarefas pesadas para Worker Threads;
  • reduzir nível de compressão usando node:zlib quando precisar de controle fino.

Veja Worker Threads no Node.js.

CompressionStream ou zlib?

Use CompressionStream quando:

  • a aplicação já trabalha com Web Streams;
  • portabilidade com navegador é importante;
  • a configuração padrão é suficiente;
  • você quer uma API simples e padronizada.

Use node:zlib quando:

  • precisa ajustar nível, janela ou estratégia;
  • usa streams clássicas em todo o projeto;
  • precisa de APIs específicas;
  • quer controle operacional detalhado.

Decompression bombs

Um arquivo pequeno pode expandir para gigabytes. Nunca descomprima conteúdo não confiável sem limites.

async function readWithLimit(stream, maxBytes) {
  const reader = stream.getReader();
  const chunks = [];
  let total = 0;

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;

      total += value.byteLength;
      if (total > maxBytes) {
        throw new Error('Saída descomprimida excedeu o limite');
      }

      chunks.push(value);
    }
  } finally {
    reader.releaseLock();
  }

  return chunks;
}

Também aplique timeout, limite de entrada e cancelamento.

Cancelamento

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5_000);

try {
  const response = await fetch(url, {
    signal: controller.signal
  });
} finally {
  clearTimeout(timeout);
}

Propague cancelamento ao pipeline quando o consumidor abandonar a operação.

Erros de formato

DecompressionStream lança erro quando o conteúdo está truncado ou incompatível. Capture a falha e não devolva detalhes internos ao cliente.

try {
  const output = input.pipeThrough(
    new DecompressionStream('gzip')
  );
  await new Response(output).arrayBuffer();
} catch {
  throw new Error('Conteúdo comprimido inválido');
}

Segurança e BREACH

Comprimir respostas que misturam segredos e entrada controlada pelo atacante pode permitir ataques de análise de tamanho. Tokens, CSRF secrets e dados altamente sensíveis não devem ser refletidos em respostas comprimidas junto com entrada do usuário sem avaliação de risco.

Content-Length

Quando a saída é produzida em streaming, o tamanho final normalmente não é conhecido antecipadamente. Não envie um Content-Length calculado sobre o conteúdo original. Use transferência apropriada ou pré-comprima o arquivo.

Testes

import test from 'node:test';
import assert from 'node:assert/strict';

test('comprime e descomprime gzip', async () => {
  const original = 'abc'.repeat(10_000);
  const source = new Blob([original]).stream();

  const compressed = source.pipeThrough(
    new CompressionStream('gzip')
  );

  const restored = compressed.pipeThrough(
    new DecompressionStream('gzip')
  );

  assert.equal(await new Response(restored).text(), original);
});

Teste dados vazios, Unicode, chunks pequenos, conteúdo truncado, cancelamento e limite excedido.

Benchmark

Meça:

  • tamanho original;
  • tamanho comprimido;
  • tempo de CPU;
  • latência total;
  • memória;
  • event loop lag;
  • throughput;
  • efeito de concorrência.

Não escolha Brotli ou gzip apenas pela taxa de compressão. O melhor formato depende de custo computacional e comportamento do cliente.

Erros comuns

  • não verificar suporte do runtime;
  • aceitar formato arbitrário;
  • comprimir arquivos já compactados;
  • esquecer Vary;
  • enviar Content-Length incorreto;
  • reunir arquivo grande em memória;
  • ignorar backpressure;
  • descomprimir sem limite;
  • usar compressão em respostas com segredos refletidos;
  • não medir CPU.

Checklist

  • runtime compatível;
  • formatos em allowlist;
  • MIME type avaliado;
  • tamanho mínimo definido;
  • backpressure preservada;
  • Vary configurado;
  • limite de saída;
  • timeout e cancelamento;
  • round trip testado;
  • benchmark realizado.

Conclusão

O CompressionStream no Node.js leva compressão para o modelo de Web Streams. A API é direta, interoperável e funciona bem em pipelines com fetch, Blob, arquivos e respostas HTTP.

Use compressão seletivamente, preserve backpressure e limite a descompressão de conteúdo não confiável. Quando precisar de configuração avançada, combine ou substitua a API por node:zlib. Com testes e métricas, a compressão reduz tráfego sem transformar CPU e memória em novos gargalos.

Consulte também a referência de CompressionStream no MDN.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita