Node.js I/O

Node.js Streams

Streams processam dados em pedaços (chunks) em vez de carregar tudo na memória. É assim que o Node copia arquivos, compacta payloads e transmite corpos HTTP de forma eficiente.

intermediate15 min readUpdated 15 de set. de 2026
compress.js
js
// compress.js
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";

await pipeline(
  createReadStream("access.log"),
  createGzip(),
  createWriteStream("access.log.gz"),
);
Tamanho do chunk
highWaterMark
Tipos
Readable, Writable, Duplex, Transform
Conexão
pipe e pipeline
Controle de fluxo
Backpressure
Iteração
for await
Ideal para
Dados grandes ou ilimitados

Por que importa

Por que streams são importantes

Memória constante

Uma stream mantém apenas um chunk por vez, permitindo processar gigabytes com apenas alguns megabytes de memória.

Backpressure natural

Um consumidor lento sinaliza ao produtor para diminuir a velocidade, evitando que fontes rápidas sobrecarreguem a memória ou um disco lento.

Pipelines compostíveis

Etapas Readable, transform e writable se encaixam, transformando processamentos complexos em cadeias legíveis.

O panorama completo

As três ideias por trás das streams

Os dados fluem em chunks, o backpressure mantém o equilíbrio entre produtor e consumidor, e o pipeline conecta as etapas.

Chunks

Fluxo

Os dados movem-se em buffers ou objetos, em vez de um único valor gigante.

Backpressure

Equilíbrio

Buffers internos e o highWaterMark impedem que os produtores ultrapassem a capacidade dos consumidores.

Pipeline

Composição

O pipeline conecta as etapas e propaga erros e a limpeza de recursos.

Streams em resumo

O núcleo das streams

Readable

Uma fonte da qual você lê, como um arquivo, socket ou corpo de requisição.

Writable

Um destino onde você escreve, como um arquivo ou resposta.

Duplex

Tanto readable quanto writable, como um TCP socket.

Transform

Uma etapa duplex que modifica os dados, como gzip ou um parser.

Backpressure

O método write retorna false e você aguarda o evento drain.

Object mode

Transmite objetos em vez de bytes para pipelines estruturados.

Uma breve historia

Streams ao longo dos anos

  1. 2010

    Chegada das Streams

    Node introduz streams para lidar com dados de forma incremental.

    10
  2. 2012

    Streams 2

    Uma API redesenhada adiciona pipe e backpressure.

    12
  3. 2017

    Streams 3

    Semânticas mais limpas e o pipeline melhoram o tratamento de erros.

    17
  4. 2018

    Iteração assíncrona

    O for await of faz a leitura de streams parecer a de arrays.

    18
  5. Hoje

    Em todo lugar

    HTTP, arquivos, compressão, crypto e muitas bibliotecas são baseados em streams.

    Hoje

O guia completo

Node.js Streams: Tudo que voce precisa saber

Por que streams são importantes

Carregar um arquivo grande na memória funciona até que o arquivo seja maior do que a memória disponível. As Streams resolvem isso processando os dados em pedaços (chunks): você lê uma parte, a processa e segue adiante, mantendo o uso de memória aproximadamente constante, independentemente do tamanho da entrada.

Streams estão em todo lugar no Node.js. Corpos de requisições e respostas HTTP, leitura e escrita de arquivos, compressão, criptografia e diversos parsers são todos streams. Compreendê-las é o que permite construir servidores e ferramentas que lidam com volumes de dados ilimitados sem travar.

Os quatro tipos de streams

Toda stream pertence a um destes quatro tipos:

  • Readable — uma fonte de onde você lê dados. Arquivos, corpos de requisições HTTP, sockets e process.stdin.
  • Writable — um destino para onde você escreve dados. Arquivos, respostas HTTP, sockets e process.stdout.
  • Duplex — tanto readable quanto writable, como um socket TCP.
  • Transform — uma stream duplex que modifica os dados enquanto eles passam, como zlib.createGzip() ou um parser de CSV.

Streams readable e writable se conectam em pipelines, e as transforms ficam posicionadas no meio.

Leitura e escrita

A maneira mais simples de consumir um readable stream é através de iteração assíncrona.

// read.js
import { createReadStream } from "node:fs";

const stream = createReadStream("access.log", { encoding: "utf8" });

for await (const chunk of stream) {
  process.stdout.write(chunk);
}

Para writable streams, chame write() e sinalize o término com end().

// write.js
import { createWriteStream } from "node:fs";

const out = createWriteStream("out.txt");

out.write("first line\n");
out.write("second line\n");
out.end();

O stream faz o buffer das escritas internamente e as descarrega (flush) de forma eficiente, portanto, você não precisa gerenciar os chunks manualmente.

Piping e pipeline

Um pipeline conecta um readable, zero ou mais transforms e um writable.

// compress.js
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";

await pipeline(
  createReadStream("access.log"),
  createGzip(),
  createWriteStream("access.log.gz"),
);

Sempre prefira pipeline em vez do encadeamento manual de .pipe(). O pipeline encaminha erros de cada etapa, destrói os streams em caso de falha e retorna uma promise, o que torna o tratamento de erros muito mais simples. O .pipe() manual não propaga erros, portanto, uma falha no início do fluxo pode deixar o pipeline travado e o destino aberto.

Backpressure

Streams possuem buffers internos. Quando você escreve dados em uma velocidade maior do que o destino consegue consumir, o buffer enche e write() retorna false.

// backpressure.js
function writeAll(stream, chunks) {
  return new Promise((resolve, reject) => {
    let i = 0;
    const next = () => {
      while (i < chunks.length) {
        const ok = stream.write(chunks[i++]);
        if (!ok) {
          stream.once("drain", next);
          return;
        }
      }
      stream.end(resolve);
    };
    stream.on("error", reject);
    next();
  });
}

Aguardar por drain antes de escrever mais dados é a forma como um produtor respeita um consumidor lento. pipeline e pipe lidam com isso automaticamente, o que é mais um motivo para utilizá-los.

Transform streams

Um transform stream aplica uma função a cada chunk. Você pode criar o seu próprio.

// upper.js
import { Transform } from "node:stream";

const upper = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  },
});

process.stdin.pipe(upper).pipe(process.stdout);

O Node fornece transforms úteis em node:zlib (gzip, deflate), node:crypto (ciphers e hashes) e em diversas bibliotecas, como parsers de CSV e JSON.

Object mode

Por padrão, as streams transportam bytes. O Object mode permite que elas transportem objetos JavaScript, o que é ideal para pipelines estruturados.

// object-mode.js
import { Readable, Transform } from "node:stream";

Readable.from([{ id: 1 }, { id: 2 }])
  .pipe(
    new Transform({
      objectMode: true,
      transform(record, _encoding, callback) {
        callback(null, { ...record, seen: true });
      },
    }),
  )
  .on("data", console.log);

No object mode, highWaterMark conta objetos em vez de bytes. É assim que são construídas as streams de linhas de banco de dados, processadores de log e pipelines de ETL.

Padrões comuns

  • Copiar um arquivo: pipeline(createReadStream(src), createWriteStream(dest)).
  • Comprimir ou criptografar: insira createGzip() ou um transform de cifra.
  • Transmitir uma resposta HTTP: direcione (pipe) um arquivo ou o resultado de uma query diretamente para res.
  • Analisar dados delimitados por linha: use um transform que realize a divisão por quebras de linha.
  • Progresso de upload: conte os bytes em um transform conforme eles passam.

Melhores práticas

  • Prefira pipeline em vez de .pipe() para tratamento de erros e limpeza.
  • Faça o stream de arquivos e respostas grandes em vez de carregá-los em buffer.
  • Respeite o backpressure; nunca ignore quando write() retornar false.
  • Ajuste o highWaterMark apenas após realizar medições.
  • Use o object mode para dados estruturados.
  • Trate error em todo stream que você criar.
  • Destrua os streams em caso de falha para que os file descriptors sejam liberados.

Erros comuns

  • Usar readFile para dados volumosos ou sem limite (unbounded).
  • Encadear .pipe() e perder erros.
  • Ignorar o backpressure e fazer o buffering de dados sem limite.
  • Esquecer que um callback de transform deve ser chamado exatamente uma vez.
  • Misturar encodings e gerar saídas corrompidas.
  • Deixar streams abertas após um erro.

Próximos passos

Streams são a forma como o Node.js lida com dados em escala. Coloque-os em prática com o guia de File System e entenda o agendamento por trás disso no guia de Event Loop. Depois, reescreva uma chamada readFile como um stream e observe o consumo de memória estabilizar.

Conectando etapas de stream

O pipeline gerencia erros, fecha cada etapa e faz a limpeza. O pipe manual não encaminha erros, então falhas podem travar a aplicação ou causar vazamentos.

Preferir
import { pipeline } from "node:stream/promises";

await pipeline(source, transform, destination);
Evitar
source.pipe(transform).pipe(destination);
// errors on source are not
// forwarded to destination

Lendo um arquivo grande

Uma stream mantém o uso de memória estável. O readFile carrega todo o arquivo no buffer, o que é aceitável para arquivos pequenos, mas perigoso para arquivos grandes.

Preferir
import { createReadStream } from "node:fs";

const stream = createReadStream("big.log");
for await (const chunk of stream) {
  handle(chunk);
}
Evitar
import { readFile } from "node:fs/promises";

// the whole file in memory
const data = await readFile("big.log");
handle(data);

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender Streams?

Nosso tutorial interativo te guia por Streams passo a passo — com quizzes e codigo real que voce pode executar no navegador.