Compactar respostas, arquivos e mensagens reduz o volume transferido e pode economizar armazenamento. O módulo Zlib no Node.js, disponível como node:zlib, oferece implementações nativas de gzip, deflate e Brotli integradas ao sistema de streams.
Compressão troca CPU por menos bytes. Um nível alto pode reduzir um pouco mais o arquivo, mas aumentar bastante a latência. Dados já compactados, como JPEG e ZIP, raramente obtêm benefício. Também é necessário limitar a descompressão para evitar consumo excessivo de memória e ataques com arquivos compactados maliciosos.
Neste guia, você aprenderá a compactar buffers e streams, criar arquivos gzip, usar Brotli, negociar conteúdo HTTP, ajustar parâmetros, cancelar pipelines e testar integridade.
Carregando o módulo
const zlib = require('node:zlib');A documentação oficial do módulo Zlib detalha formatos, constantes e opções. Para entender os fluxos usados pela API, consulte Streams no Node.js e Buffer no Node.js.
Compactando um Buffer com gzip
const { promisify } = require('node:util');
const gzip = promisify(zlib.gzip);
async function compressJson(data) {
const input = Buffer.from(JSON.stringify(data));
return gzip(input);
}As funções baseadas em callback podem ser promisificadas. Esse padrão é adequado para conteúdo pequeno e limitado. Para grandes volumes, use streams.
Descompactando gzip
const gunzip = promisify(zlib.gunzip);
const compressed = await compressJson({ status: 'ok' });
const plain = await gunzip(compressed);
console.log(JSON.parse(plain.toString('utf8')));Não descompacte conteúdo externo sem limite. Um arquivo pequeno pode expandir para um volume enorme.
Compactando arquivos com pipeline
const fs = require('node:fs');
const { pipeline } = require('node:stream/promises');
async function createGzip(source, destination) {
await pipeline(
fs.createReadStream(source),
zlib.createGzip(),
fs.createWriteStream(destination)
);
}O arquivo é processado em chunks, mantendo a memória previsível. Se uma etapa falha, pipeline() encerra as streams relacionadas.
Descompactando um arquivo
async function extractGzip(source, destination) {
await pipeline(
fs.createReadStream(source),
zlib.createGunzip(),
fs.createWriteStream(destination)
);
}Grave primeiro em um arquivo temporário. Somente depois da conclusão renomeie para o destino final, evitando que consumidores enxerguem conteúdo incompleto. O artigo sobre File System no Node.js apresenta gravação atômica.
Gzip, deflate e Brotli
- gzip: amplamente compatível em HTTP e arquivos.
- deflate: formato baseado em DEFLATE, com diferenças de encapsulamento.
- deflateRaw: dados DEFLATE sem cabeçalho zlib.
- Brotli: geralmente eficiente para texto web, com custo configurável.
O formato deve ser conhecido pelas duas partes. Não tente adivinhar apenas pela extensão.
Usando Brotli
const brotliCompress = promisify(zlib.brotliCompress);
const brotliDecompress = promisify(zlib.brotliDecompress);
const compressed = await brotliCompress(
Buffer.from('conteúdo repetitivo')
);
const original = await brotliDecompress(compressed);Brotli possui níveis de qualidade mais altos, mas configurações máximas podem ser muito lentas para respostas dinâmicas.
Ajustando gzip
const gzipStream = zlib.createGzip({
level: zlib.constants.Z_BEST_SPEED
});Z_BEST_SPEED prioriza velocidade; Z_BEST_COMPRESSION prioriza tamanho. O valor padrão costuma ser um bom equilíbrio. Meça com dados reais.
Ajustando Brotli
const brotli = zlib.createBrotliCompress({
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 4
}
});Para assets estáticos gerados no build, um nível maior pode fazer sentido. Para respostas dinâmicas, níveis moderados reduzem CPU e tempo até o primeiro byte.
Compressão em uma resposta HTTP
const http = require('node:http');
http.createServer((req, res) => {
const accepts = req.headers['accept-encoding'] || '';
const body = Buffer.from(JSON.stringify({ message: 'Olá' }));
res.setHeader('content-type', 'application/json');
res.setHeader('vary', 'accept-encoding');
if (accepts.includes('br')) {
res.setHeader('content-encoding', 'br');
zlib.brotliCompress(body, (error, output) => {
if (error) return res.destroy(error);
res.end(output);
});
return;
}
if (accepts.includes('gzip')) {
res.setHeader('content-encoding', 'gzip');
zlib.gzip(body, (error, output) => {
if (error) return res.destroy(error);
res.end(output);
});
return;
}
res.end(body);
}).listen(3000);O cabeçalho Vary evita que caches entreguem uma versão compactada a clientes incompatíveis.
Nem toda resposta deve ser compactada
Evite compactar conteúdo pequeno, já compactado ou sensível quando a resposta também inclui dados controlados pelo atacante. Ataques de compressão podem explorar diferenças de tamanho para inferir segredos.
Defina um tamanho mínimo, tipos permitidos e política para respostas com tokens ou dados refletidos.
Compressão em frameworks
Frameworks oferecem middlewares que negociam Accept-Encoding, definem threshold e tratam headers. Mesmo assim, confirme a configuração do proxy, pois CDN ou servidor frontal pode realizar compressão novamente.
Evite compressão dupla
Se a aplicação envia Content-Encoding: gzip, o proxy não deve compactar outra vez. Compressão dupla gera conteúdo inválido ou desperdício. Inspecione os headers no cliente final.
Descompressão automática
Alguns clientes HTTP descompactam respostas automaticamente e removem ou preservam headers de maneiras diferentes. Não compare apenas o tamanho do Buffer recebido sem saber o comportamento da biblioteca.
Protegendo contra expansão excessiva
Ao receber conteúdo compactado, limite bytes de entrada, bytes descompactados e tempo. Uma Transform de contagem pode interromper o pipeline:
const { Transform } = require('node:stream');
function limitBytes(maxBytes) {
let total = 0;
return new Transform({
transform(chunk, encoding, callback) {
total += chunk.length;
if (total > maxBytes) {
callback(new Error('Conteúdo descompactado excedeu o limite'));
return;
}
callback(null, chunk);
}
});
}await pipeline(
source,
zlib.createGunzip(),
limitBytes(50 * 1024 * 1024),
destination
);Cancelamento
const controller = new AbortController();
await pipeline(
fs.createReadStream(source),
zlib.createGzip(),
fs.createWriteStream(destination),
{ signal: controller.signal }
);Ao cancelar, remova o arquivo parcial. Consulte AbortController no Node.js.
Concorrência e CPU
Muitas compressões simultâneas aumentam CPU e latência. Limite concorrência, use cache para respostas repetidas e pré-comprima assets estáticos. Monitore duração e tamanho antes e depois.
Cache de versões compactadas
Para conteúdo estável, armazene versões .gz e .br no build ou na CDN. A aplicação apenas escolhe o arquivo adequado, evitando compressão a cada requisição.
Integridade
Gzip possui verificação interna, mas isso não substitui autenticação criptográfica quando o conteúdo precisa ser protegido contra alteração maliciosa. Para hashes e assinaturas, veja Crypto no Node.js.
Testando
Teste round-trip, arquivos vazios, conteúdo incompressível, dados corrompidos, cancelamento, limite excedido e concorrência. Confirme headers HTTP com e sem suporte a Brotli e gzip.
Erros comuns
- Usar nível máximo em respostas dinâmicas.
- Compactar JPEG, ZIP ou vídeo sem medir.
- Esquecer
Vary: Accept-Encoding. - Permitir descompressão sem limite.
- Compactar duas vezes no proxy e na aplicação.
- Carregar arquivo enorme antes de compactar.
- Não remover destino parcial após falha.
- Ignorar custo de CPU sob concorrência.
Boas práticas
- Use streams para conteúdo grande.
- Meça taxa de compressão e duração.
- Defina threshold para respostas HTTP.
- Prefira níveis moderados em tempo real.
- Pré-comprima assets estáticos.
- Limite expansão e tempo de descompressão.
- Configure
Varycorretamente. - Evite compressão dupla.
- Controle concorrência.
- Teste conteúdo corrompido.
Conclusão
O Zlib no Node.js integra gzip, deflate e Brotli ao modelo de streams. Ele permite compactar arquivos e respostas com memória previsível e configuração de desempenho.
O melhor resultado vem de medir. Escolha formato e nível conforme o tipo de conteúdo, limite descompressão e evite trabalho duplicado com proxy ou CDN. Com pipelines e observabilidade, a compressão reduz bytes sem transformar CPU em um novo gargalo.




