JavaScript

Streams em Node.js: Leitura, Escrita e Transformação de Dados: Do Básico ao Avançado

7 min de leitura

Streams em Node.js: Leitura, Escrita e Transformação de Dados: Do Básico ao Avançado

Entendendo Streams: A Base Conceitual Streams são sequências contínuas de dados que fluem através de sua aplicação em pequenos pedaços, em vez de carregar tudo em memória. Imagine um tubo por onde a água flui gradualmente—é exatamente assim que streams funcionam em Node.js. Essa abordagem é essencial para aplicações que precisam processar grandes volumes de dados (arquivos gigantes, uploads, logs em tempo real) sem consumir memória excessiva. O modelo de streams em Node.js segue o padrão de Readable (leitura), Writable (escrita), Duplex (ambos) e Transform (transformação). Cada tipo oferece um nível de controle diferente sobre o fluxo de dados. A principal vantagem é o backpressure—o sistema sabe quando pausar e retomar o envio de dados automaticamente, evitando gargalos. Leitura de Dados com Streams Readable Criando um Stream Readable A forma mais comum de ler arquivos é usar . Este exemplo lê um arquivo em chunks de 64KB: Recebido chunk de ${chunk.length} bytes Controlando o Fluxo Você pode pausar e retomar

<h2>Entendendo Streams: A Base Conceitual</h2>

<p>Streams são sequências contínuas de dados que fluem através de sua aplicação em pequenos pedaços, em vez de carregar tudo em memória. Imagine um tubo por onde a água flui gradualmente—é exatamente assim que streams funcionam em Node.js. Essa abordagem é essencial para aplicações que precisam processar grandes volumes de dados (arquivos gigantes, uploads, logs em tempo real) sem consumir memória excessiva.</p>

<p>O modelo de streams em Node.js segue o padrão de Readable (leitura), Writable (escrita), Duplex (ambos) e Transform (transformação). Cada tipo oferece um nível de controle diferente sobre o fluxo de dados. A principal vantagem é o backpressure—o sistema sabe quando pausar e retomar o envio de dados automaticamente, evitando gargalos.</p>

<h2>Leitura de Dados com Streams Readable</h2>

<h3>Criando um Stream Readable</h3>

<p>A forma mais comum de ler arquivos é usar <code>fs.createReadStream()</code>. Este exemplo lê um arquivo em chunks de 64KB:</p>

<pre><code class="language-javascript">const fs = require(&#039;fs&#039;);

const readable = fs.createReadStream(&#039;arquivo-grande.txt&#039;, {

encoding: &#039;utf8&#039;,

highWaterMark: 64 * 1024 // 64KB por chunk

});

readable.on(&#039;data&#039;, (chunk) =&gt; {

console.log(Recebido chunk de ${chunk.length} bytes);

// Processa cada chunk

});

readable.on(&#039;end&#039;, () =&gt; {

console.log(&#039;Arquivo lido completamente&#039;);

});

readable.on(&#039;error&#039;, (err) =&gt; {

console.error(&#039;Erro na leitura:&#039;, err);

});</code></pre>

<h3>Controlando o Fluxo</h3>

<p>Você pode pausar e retomar streams manualmente. Isso é útil quando precisa controlar a taxa de consumo:</p>

<pre><code class="language-javascript">const readable = fs.createReadStream(&#039;dados.json&#039;);

readable.on(&#039;data&#039;, (chunk) =&gt; {

readable.pause();

setTimeout(() =&gt; {

console.log(&#039;Processando chunk...&#039;);

readable.resume();

}, 1000);

});</code></pre>

<h2>Escrita e Transformação de Dados</h2>

<h3>Streams Writable e Pipe</h3>

<p>O método <code>pipe()</code> conecta um stream readable diretamente a um writable, gerenciando automaticamente o backpressure:</p>

<pre><code class="language-javascript">const fs = require(&#039;fs&#039;);

const readable = fs.createReadStream(&#039;origem.txt&#039;);

const writable = fs.createWriteStream(&#039;destino.txt&#039;);

readable.pipe(writable);

writable.on(&#039;finish&#039;, () =&gt; {

console.log(&#039;Arquivo copiado com sucesso&#039;);

});</code></pre>

<h3>Transform Streams para Processamento</h3>

<p>Transform streams modificam dados enquanto fluem. Aqui convertemos texto para maiúsculas em tempo real:</p>

<pre><code class="language-javascript">const { Transform } = require(&#039;stream&#039;);

const fs = require(&#039;fs&#039;);

const maiusculasTransform = new Transform({

transform(chunk, encoding, callback) {

const maiorizado = chunk.toString().toUpperCase();

callback(null, maiorizado);

}

});

fs.createReadStream(&#039;entrada.txt&#039;)

.pipe(maiusculasTransform)

.pipe(fs.createWriteStream(&#039;saida.txt&#039;));</code></pre>

<p>Para casos mais complexos, como parsear JSON line-delimited:</p>

<pre><code class="language-javascript">const { Transform } = require(&#039;stream&#039;);

const fs = require(&#039;fs&#039;);

const parseJSON = new Transform({

objectMode: true,

transform(chunk, encoding, callback) {

try {

const linha = chunk.toString().trim();

if (linha) {

const obj = JSON.parse(linha);

callback(null, obj);

} else {

callback();

}

} catch (err) {

callback(err);

}

}

});

fs.createReadStream(&#039;dados.jsonl&#039;)

.pipe(parseJSON)

.on(&#039;data&#039;, (obj) =&gt; {

console.log(&#039;Objeto processado:&#039;, obj);

});</code></pre>

<h2>Padrões Avançados e Boas Práticas</h2>

<h3>Composição de Streams</h3>

<p>Combine múltiplos transforms para criar pipelines poderosos. Aqui filtramos e transformamos dados sequencialmente:</p>

<pre><code class="language-javascript">const { Transform } = require(&#039;stream&#039;);

const fs = require(&#039;fs&#039;);

const filtro = new Transform({

objectMode: true,

transform(obj, encoding, callback) {

if (obj.idade &gt;= 18) {

callback(null, obj);

} else {

callback();

}

}

});

const formatador = new Transform({

objectMode: true,

transform(obj, encoding, callback) {

const formatado = ${obj.nome} (${obj.idade} anos)\n;

callback(null, formatado);

}

});

fs.createReadStream(&#039;usuarios.jsonl&#039;)

.pipe(parseJSON)

.pipe(filtro)

.pipe(formatador)

.pipe(fs.createWriteStream(&#039;maiores.txt&#039;));</code></pre>

<h3>Tratamento de Erros</h3>

<p>Sempre implemente tratamento robusto de erros em pipelines:</p>

<pre><code class="language-javascript">const fs = require(&#039;fs&#039;);

const readable = fs.createReadStream(&#039;origem.txt&#039;);

const writable = fs.createWriteStream(&#039;destino.txt&#039;);

readable

.on(&#039;error&#039;, (err) =&gt; console.error(&#039;Erro na leitura:&#039;, err.message))

.pipe(writable)

.on(&#039;error&#039;, (err) =&gt; console.error(&#039;Erro na escrita:&#039;, err.message))

.on(&#039;finish&#039;, () =&gt; console.log(&#039;Pipeline concluído&#039;));</code></pre>

<p>Ou use a sintaxe moderna com <code>pipeline()</code>:</p>

<pre><code class="language-javascript">const { pipeline } = require(&#039;stream&#039;);

const fs = require(&#039;fs&#039;);

pipeline(

fs.createReadStream(&#039;origem.txt&#039;),

fs.createWriteStream(&#039;destino.txt&#039;),

(err) =&gt; {

if (err) {

console.error(&#039;Erro no pipeline:&#039;, err.message);

} else {

console.log(&#039;Pipeline concluído com sucesso&#039;);

}

}

);</code></pre>

<h2>Conclusão</h2>

<p><strong>Streams são fundamentais</strong> em Node.js para trabalhar eficientemente com dados grandes. O trio Readable, Transform e Writable, combinado com <code>pipe()</code>, permite criar aplicações que processam informações de forma elegante e otimizada em memória.</p>

<p><strong>Sempre prefira streams</strong> sobre carregar tudo em buffer para manipulação de arquivos, uploads/downloads e processamento de dados em tempo real. Use <code>pipeline()</code> para melhor tratamento de erros em versões modernas do Node.js.</p>

<p><strong>Domine backpressure e composição</strong> de streams para construir arquiteturas robustas. Pequenas práticas—como definir <code>highWaterMark</code> apropriadamente e encadear transforms—fazem diferença significativa em produção.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://nodejs.org/api/stream.html" target="_blank" rel="noopener noreferrer">Documentação Oficial: Stream API do Node.js</a></li>

<li><a href="https://nodejs.org/en/docs/guides/backpressuring-in-streams" target="_blank" rel="noopener noreferrer">Node.js Best Practices: Streams</a></li>

<li><a href="https://www.nodejsdesignpatterns.com/" target="_blank" rel="noopener noreferrer">Readable Streams - Node.js Design Patterns</a></li>

<li><a href="https://github.com/substack/stream-handbook" target="_blank" rel="noopener noreferrer">Stream Handbook - Substack</a></li>

<li><a href="https://javascript.info/streams" target="_blank" rel="noopener noreferrer">JavaScript.info - Streams</a></li>

</ul>

Comentários

Mais em JavaScript

Como Usar Web Storage em JavaScript: localStorage, sessionStorage e IndexedDB em Produção
Como Usar Web Storage em JavaScript: localStorage, sessionStorage e IndexedDB em Produção

Web Storage em JavaScript: localStorage, sessionStorage e IndexedDB A persist...

Boas Práticas de Express.js: Roteamento, Middlewares e Estrutura de APIs REST para Times Ágeis
Boas Práticas de Express.js: Roteamento, Middlewares e Estrutura de APIs REST para Times Ágeis

Express.js: Fundamentos e Arquitetura Express.js é um framework web minimalis...

React: Fundamentos, JSX e Componentes Funcionais: Do Básico ao Avançado
React: Fundamentos, JSX e Componentes Funcionais: Do Básico ao Avançado

React: O que é e por que aprender React é uma biblioteca JavaScript desenvolv...