Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Node.js: dominando streams e processando arquivos pesados

Node.js: dominando streams e processando arquivos pesados

Processando arquivos com Node.js - Apresentação

Boas-vindas ao curso de Streams com o Node.js!

Audiodescrição: Thiago Bussola é um homem branco, com cabelo curto e barba. Estou vestindo uma camiseta rosa e óculos de grau, e atrás dele há uma estante e um armário branco iluminado por luzes azuis.

Este conteúdo é destinado a quem já possui experiência na criação de APIs com Node.js, seja utilizando Express ou outro framework, e deseja aprender a trabalhar com arquivos mais pesados, como relatórios, arquivos de vídeo ou até mesmo arquivos de áudio.

Neste curso, nós aprenderemos a entender como o loop de eventos do Node.js se comporta com arquivos mais pesados, a ler, escrever e manipular arquivos com uma grande quantidade de linhas, a fazer upload e streaming de arquivos de vídeo, e a integrar nossa aplicação com uma API da OpenAI para expandir as possibilidades em nosso serviço de streaming. Além disso, abordaremos o tratamento de erros baseado nos eventos das nossas streams.

Tudo isso será explorado em um projeto prático, no qual simularemos uma plataforma de videoaulas.

O que você precisa saber?

É necessário saber como desenvolver APIs REST usando Express ou outro framework e ter um entendimento básico de manipulação de arquivos de texto com Node.js.

Aproveite os recursos da nossa plataforma, pois além dos vídeos, oferecemos o apoio do fórum e uma comunidade no Discord para auxiliar nos estudos.

Vamos estudar?

Processando arquivos com Node.js - Lendo arquivos com streams

Na atividade de preparação do ambiente, executamos um script para criar um arquivo que simula, por exemplo, um relatório de um serviço de streaming, contendo:

O arquivo acabou ficando bem pesado, e nosso script foi criado para ter pelo menos 2 GB. Nesta aula, tentaremos fazer a leitura de um arquivo grande como esse, utilizando o Node.js da forma padrão, que é utilizando o readFile(). Para isso, vamos criar uma pasta chamada "services" dentro da pasta "src", e um arquivo chamado file.services.ts.

Nesse arquivo, criaremos uma constante chamada filename, que receberá o nome do arquivo que queremos ler, largeFile.csv. Em seguida, criaremos uma função assíncrona chamada brokenApp(), que não receberá nenhum parâmetro. Nessa função, chamaremos o await readFile, importando-o do fs/promises, e passaremos o nome do arquivo armazenado na constante filename. O segundo parâmetro, que é opcional, será o encoding do arquivo, que pode ser utf-8, já que está em português brasileiro.

const filename = "largeFile.csv";

async function brokenApp() {
    await readFile(filename, "utf8");
}

brokenApp();

A partir da linha 9, chamaremos a função brokenApp(), salvaremos o arquivo, abriremos o terminal e executaremos o arquivo. Como estamos utilizando TypeScript, precisamos de um transpilador para lê-lo. Usaremos o npx, que não requer instalação da dependência, e executaremos com o comando:

npx ts-node-dev src/services/file.service.ts

O Node.js lançará um erro indicando que o arquivo não pode ser maior do que 2 GB, e que não consegue manipulá-lo. Isso ocorre devido a uma trava de segurança padrão para evitar a leitura ou manipulação de arquivos desse tamanho. O Node.js funciona com um sistema chamado Event Loop, que é uma parte single-thread da aplicação, responsável por escutar eventos e delegar funções para outras partes do sistema. Embora o loop seja single-thread, outras ações, como entrada e saída, não são. Se travarmos o loop de eventos, não conseguimos delegar outras tarefas na aplicação.

Existem técnicas para trabalhar com arquivos desse tamanho, como o uso de streams. Streams são funções que permitem a leitura de arquivos em pequenos pedaços, sem carregar tudo na memória de uma vez só, sendo processado incrementalmente. Vamos ver como fica a leitura desse arquivo usando streams, ao invés de carregar o arquivo inteiro na memória, como o readFile() faz.

Antes da linha com brokenApp(), criaremos outra função, a readLargeFile(), que também não recebe parâmetros, além implementarmos um createReadStream do módulo fs, passando o arquivo que queremos ler e um objeto com a propriedade encoding, utilizando utf8:

function readLargeFile() {
    createReadStream(filename, { encoding: "utf8" });
}

//brokenApp();

Vamos executar a leitura do arquivo sem armazená-lo em uma constante, apenas para verificar a velocidade com que o Node.js consegue ler um arquivo desse tamanho. Comentaremos a função brokenApp() e chamaremos readLargeFile(). Após salvar, executaremos novamente o arquivo file.service.ts.

A leitura foi tão rápida que o terminal nem conseguiu calcular o tempo, ocorrendo em menos de um segundo. Agora, incluiremos uma constante para imprimir todo o processo de leitura e verificar se realmente está acontecendo.

Criaremos uma constante chamada readStream, que receberá createReadStream(). Utilizaremos readStream.on(), que permite escutar eventos dentro da stream. Queremos ouvir o evento de data, ou seja, os dados que chegam na stream. Passaremos uma função anônima com um parâmetro chunk, utilizando uma arrow function, e dentro dela colocaremos console.log(chunk). O chunk é o pequeno pedaço que estamos tentando ler.

function readLargeFile() {
    const readStream = createReadStream(filename, { encoding: "utf8" });
    
    readStream.on("data", (chunk) => {
        console.log(chunk);
    });
}

Durante o processo de leitura, ele parará e imprimirá cada chunk. Como aumentamos o processo de entrada e saída, é normal que demore um pouco mais, mas conseguiremos verificar se todo o arquivo está sendo lido corretamente. Executaremos novamente o comando no terminal.

Vimos que está lendo, e não passaremos pela leitura do arquivo inteiro. Nosso CSV está sendo lido corretamente, passando o nome da pessoa, a série que ela assistiu e a nota atribuída, assim como o nosso CSV original.

A seguir, aprenderemos como manipular os dados do nosso relatório, para irmos além da leitura. Até a próxima!

Processando arquivos com Node.js - Processando GB de arquivos

Anteriormente, aprendemos a fazer a leitura de um grande arquivo usando as Node.js streams. No entanto, apenas ler um arquivo ou um relatório não é o suficiente. No nosso dia a dia de trabalho, muitas vezes queremos processar esse tipo de relatório, seja adicionando novos campos ou transformando alguns dados pré-existentes. Vamos transformar os dados, modificando o que já existe e adicionando uma nova coluna no nosso CSV.

No caso dos nomes, podemos usar o comando cat para visualizar o arquivo largeFile.csv. Isso é útil, pois abrir o arquivo no VSCode ou em um editor de CSV pode causar lentidão. Observamos que o nome e o sobrenome das pessoas começam com letras maiúsculas. Supondo que nosso banco de dados seja case sensitive, queremos normalizar os dados, convertendo todos para minúsculas ou maiúsculas.

Além disso, cada campo está separado por vírgula, e desejamos incluir um campo adicional que registre a data em que fizemos a alteração no nome ou qualquer outra modificação no relatório.

Vamos minimizar o terminal e definir a lógica que utilizaremos. Começaremos transformando cada vírgula em um índice de um array. Vamos pegar o primeiro índice, zero, converter todo o valor para maiúsculas, criar um novo campo para a data e, em seguida, juntar novamente os dados usando vírgulas para recriar a "string" do CSV.

Para criar a lógica e esse processo de transformação, abaixo da implementação do readLargeFile() e antes da linha com //brokenApp();, vamos criar uma função chamada transformCsvLine(), que receberá um parâmetro do tipo string, representando a linha do CSV. Dentro dessa função, criaremos uma constante chamada parts, que receberá a linha do CSV dividida em um array, usando o método split() com a vírgula como separador.

A lógica será processar o primeiro índice apenas se o array tiver três itens, ou seja, se o CSV tiver três colunas. Caso contrário, não faremos a transformação, pois faltam dados no relatório. Usaremos um if para verificar se parts.length é igual a 3. Se for, processaremos o dado.

Vamos pegar o parts no índice 0, que contém o nome da pessoa, e aplicar trim() para remover espaços em branco, seguido de toUpperCase() para converter para maiúsculas. Caso desejemos converter para minúsculas, usamos toLowerCase().

Em seguida, criaremos uma constante alterationDate, que receberá um new Date() formatado como texto no formato ISO, usando .toISOString(). Retornaremos um array com os índices originais e o alterationDate como quarto índice, usando o operador de espalhamento ... para incluir os elementos de parts.

Por fim, usaremos join() para juntar os elementos do array em uma string, separando-os por vírgulas, e adicionaremos \n para quebrar a linha. Caso a linha não atenda à nossa regra, retornaremos a linha original com uma quebra de linha. O trecho de código, então, ficará da seguinte forma:

function transformCsvLine(line: string) {
    const parts = line.split(",")
    if(parts.length === 3) {
        parts[0] = parts[0].trim().toUpperCase()
        const alterationDate = new Date().toISOString()
        return [...parts, alterationDate].join(",") + "\n"
    }
    return line + "\n";
}

Adiante, aprenderemos como aplicar o processo de transformação usando a lógica que acabamos de criar. Vejo vocês a seguir!

Sobre o curso Node.js: dominando streams e processando arquivos pesados

O curso Node.js: dominando streams e processando arquivos pesados possui 94 minutos de vídeos, em um total de 47 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas