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

Web Streams API no Node.js

Atualizado em: 13 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

A Web Streams API no Node.js oferece interfaces padronizadas para produzir, transformar e consumir dados em fluxo. Ela usa classes como ReadableStream, WritableStream e TransformStream, também disponíveis em navegadores e runtimes de edge.

O Node.js já possui streams tradicionais há muitos anos. As Web Streams não substituem automaticamente essas APIs, mas facilitam interoperabilidade com fetch(), Response, Request e bibliotecas que seguem padrões da Web. Com adaptadores, é possível conectar os dois ecossistemas.

Neste guia, você aprenderá a criar streams legíveis e graváveis, controlar backpressure, usar readers, writers, piping, transformações, cancelamento, byte streams, conversão para streams do Node.js e práticas para evitar vazamentos ou consumo excessivo de memória.

O que são Web Streams?

Web Streams são APIs definidas em padrões da plataforma Web. A documentação oficial de Web Streams no Node.js mostra classes e adaptadores disponíveis. A documentação da Streams API na MDN explica o modelo compartilhado com navegadores.

Para o modelo tradicional do runtime, consulte Streams no Node.js. Para dados binários, veja Buffer no Node.js.

ReadableStream básico

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue('primeiro');
    controller.enqueue('segundo');
    controller.close();
  }
});

O controller adiciona chunks e encerra o fluxo. Os chunks podem ser strings, objetos ou bytes, dependendo do contrato.

Consumindo com reader

const reader = stream.getReader();

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

Um stream bloqueado por um reader não pode ser consumido simultaneamente por outro reader comum.

Iteração assíncrona

for await (const chunk of stream) {
  console.log(chunk);
}

O suporte depende da versão. Iteração assíncrona simplifica o consumo e mantém processamento sequencial.

Produção sob demanda

let current = 0;

const stream = new ReadableStream({
  pull(controller) {
    current += 1;
    controller.enqueue(current);

    if (current === 100) {
      controller.close();
    }
  }
});

pull() é chamado quando o consumidor sinaliza capacidade. Isso ajuda a respeitar backpressure.

Backpressure

Backpressure impede que o produtor gere dados muito mais rápido que o consumidor. O controller expõe desiredSize:

if (controller.desiredSize > 0) {
  controller.enqueue(nextChunk());
}

O valor é uma indicação, não uma garantia absoluta. O produtor deve parar quando não há demanda.

Queuing strategy

const stream = new ReadableStream(
  source,
  new CountQueuingStrategy({
    highWaterMark: 10
  })
);

O highWaterMark define quanta fila é desejada. Valores altos aumentam memória; valores baixos podem reduzir throughput.

ByteLengthQueuingStrategy

const strategy = new ByteLengthQueuingStrategy({
  highWaterMark: 64 * 1024
});

Essa estratégia mede o tamanho em bytes quando chunks possuem byteLength.

Cancelamento

const stream = new ReadableStream({
  cancel(reason) {
    console.log('Cancelado:', reason);
    closeExternalResource();
  }
});

Quando o consumidor cancela, libere arquivos, sockets, timers e listeners.

AbortSignal

Operações de piping podem aceitar sinal em versões compatíveis:

const controller = new AbortController();

await readable.pipeTo(writable, {
  signal: controller.signal
});

Veja AbortController no Node.js para timeouts e sinais compostos.

WritableStream

const writable = new WritableStream({
  write(chunk) {
    console.log('Recebido:', chunk);
  },
  close() {
    console.log('Concluído');
  },
  abort(reason) {
    console.error('Abortado:', reason);
  }
});

Usando writer

const writer = writable.getWriter();

try {
  await writer.write('A');
  await writer.write('B');
  await writer.close();
} finally {
  writer.releaseLock();
}

Aguardar write() permite respeitar a capacidade do destino.

writer.ready

await writer.ready;
await writer.write(chunk);

ready resolve quando a pressão diminui. Em um loop de produção, use essa Promise para não acumular dados.

TransformStream

const uppercase = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(String(chunk).toUpperCase());
  }
});

TransformStream possui lado gravável e lado legível.

Pipeline

await source
  .pipeThrough(uppercase)
  .pipeTo(destination);

Erros e cancelamentos são propagados conforme as opções do piping.

Transformação assíncrona

const transform = new TransformStream({
  async transform(chunk, controller) {
    const result = await processChunk(chunk);
    controller.enqueue(result);
  }
});

O processamento sequencial pode limitar concorrência. Se paralelizar, preserve ordem quando necessário e imponha limite de tarefas.

TextEncoderStream

const encoded = textReadable.pipeThrough(
  new TextEncoderStream()
);

Strings são convertidas em bytes UTF-8.

TextDecoderStream

const decoded = byteReadable.pipeThrough(
  new TextDecoderStream('utf-8')
);

O decoder preserva caracteres multibyte divididos entre chunks.

CompressionStream

Runtimes compatíveis podem oferecer CompressionStream:

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

Verifique suporte e formatos. Para APIs nativas tradicionais, consulte Zlib no Node.js.

DecompressionStream

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

Limite o tamanho descompactado para evitar ataques de compressão.

Fetch e Response

const response = await fetch(url);

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

for await (const chunk of response.body) {
  consume(chunk);
}

O corpo de Response é uma ReadableStream. Isso permite processar respostas grandes sem carregar tudo em memória.

Response.text() e memória

response.text() acumula todo o conteúdo. Para arquivos grandes ou fluxos contínuos, consuma o body em chunks.

Criando uma Response

const response = new Response(stream, {
  headers: {
    'content-type': 'text/plain; charset=utf-8'
  }
});

Esse padrão é útil em runtimes que usam Request e Response como interfaces de servidor.

Tee

const [first, second] = stream.tee();

tee() divide o fluxo. Se um consumidor for lento, dados podem ser acumulados para ele. Use com cuidado em streams grandes.

pipeTo()

await readable.pipeTo(writable, {
  preventClose: false,
  preventAbort: false,
  preventCancel: false
});

Opções controlam propagação. Alterá-las sem entender pode deixar recursos abertos.

Erros no produtor

controller.error(new Error('Falha na origem'));

Depois de error, o stream não aceita novos chunks. Libere recursos externos.

Erros no consumidor

Um erro em WritableStream pode cancelar a origem durante piping. Registre o contexto e garanta cleanup idempotente.

Byte streams

const bytes = new ReadableStream({
  type: 'bytes',
  pull(controller) {
    const chunk = new Uint8Array([1, 2, 3]);
    controller.enqueue(chunk);
    controller.close();
  }
});

Byte streams oferecem otimizações e readers BYOB em cenários compatíveis.

BYOB reader

const reader = bytes.getReader({ mode: 'byob' });
const target = new Uint8Array(1024);
const result = await reader.read(target);

Bring Your Own Buffer reduz alocações, mas exige atenção a views e tamanhos.

Conversão de stream Node para Web

const { Readable } = require('node:stream');

const webReadable = Readable.toWeb(nodeReadable);

O suporte depende da versão e do tipo de stream.

Conversão de Web para Node

const nodeReadable = Readable.fromWeb(webReadable);

Teste tipos de chunks e tratamento de erros. Nem toda semântica é idêntica.

Writable adapters

O módulo stream também oferece adaptadores para Writable em versões modernas. Eles ajudam a integrar bibliotecas antigas e novas.

Objetos versus bytes

Streams tradicionais do Node.js possuem objectMode. Web Streams aceitam valores JavaScript, mas estratégias e consumidores precisam concordar com o formato. Documente se cada chunk é objeto, string ou Uint8Array.

Server-Sent Events

Web Streams podem produzir eventos contínuos:

const encoder = new TextEncoder();

const stream = new ReadableStream({
  start(controller) {
    const timer = setInterval(() => {
      controller.enqueue(
        encoder.encode(`data: ${Date.now()}\n\n`)
      );
    }, 1000);

    return () => clearInterval(timer);
  }
});

O cleanup precisa ser ligado ao cancelamento. Veja Server-Sent Events com Node.js.

Limites

Mesmo com streaming, cada chunk precisa de limite. Um parser que aceita linhas infinitas ou objetos gigantes continua vulnerável.

Timeouts

Defina prazo para início, inatividade e duração total. Cancelar o pipeline deve fechar a origem.

Observabilidade

Registre:

  • bytes processados;
  • chunks;
  • duração;
  • tempo bloqueado por backpressure;
  • cancelamentos;
  • erros;
  • origem e destino sanitizados.

Testes

Cubra:

  • stream vazio;
  • múltiplos chunks;
  • Unicode dividido;
  • consumidor lento;
  • cancelamento;
  • erro de origem;
  • erro de destino;
  • transformação assíncrona;
  • conversão Node/Web;
  • limite de memória.

Erros comuns

  • Não liberar lock: o stream fica indisponível.
  • Ignorar desiredSize: a fila cresce.
  • Usar tee em fluxo grande: um consumidor lento acumula dados.
  • Carregar response.text(): todo o corpo vai para memória.
  • Não tratar cancelamento: sockets e timers permanecem ativos.
  • Misturar tipos de chunk: consumidores falham.
  • Assumir semântica idêntica: adapters exigem testes.

Boas práticas

  • Defina o tipo dos chunks.
  • Respeite backpressure.
  • Libere locks.
  • Implemente cancelamento.
  • Use limites de tamanho.
  • Defina timeouts.
  • Evite acumular todo o fluxo.
  • Teste adapters.
  • Monitore bytes e duração.
  • Feche recursos em todos os caminhos.

Conclusão

A Web Streams API no Node.js oferece uma interface portável para fluxos legíveis, graváveis e transformações, integrando-se com fetch, Request e Response.

O principal benefício aparece quando a aplicação respeita backpressure, cancelamento e limites. Com adaptadores para streams tradicionais, é possível modernizar integrações gradualmente. Ao testar tipos de chunks, erros e consumidores lentos, Web Streams tornam o processamento de dados grandes mais eficiente e interoperável sem sacrificar a estabilidade do serviço.

Os 10 Melhores Cursos de Programação de 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