Fundamentos do Node.js

0/23 aulas0%
pratica

Módulo 'fs': Trabalhando com o Sistema de Arquivos

Aprenda sobre módulo 'fs': trabalhando com o sistema de arquivos

40 min
Aula 5 de 6

Módulo 'fs': Trabalhando com o Sistema de Arquivos 📁

Olá, futuro expert em Node.js! 👋 Nesta aula prática, vamos mergulhar no coração da interação do Node.js com o sistema de arquivos do seu computador: o módulo fs (File System).

O módulo fs é um dos módulos core mais fundamentais do Node.js. Ele permite que você leia, escreva, atualize, exclua e gerencie arquivos e diretórios de forma programática. Se você precisa lidar com persistência de dados em arquivos, carregar configurações ou criar ferramentas de linha de comando, o fs será seu melhor amigo!

🚀 Introdução ao Módulo fs

O Node.js, por natureza, é assíncrono e não-bloqueante. Isso significa que a maioria das operações de I/O (Input/Output), como a leitura de um arquivo, são projetadas para não "travar" a execução do seu programa enquanto esperam a operação ser concluída. O módulo fs segue essa filosofia, oferecendo duas abordagens principais para quase todas as suas funções:

  1. Assíncrona (com Callbacks ou Promises): Esta é a forma preferida e recomendada. A função retorna imediatamente, e o resultado (ou erro) é tratado por uma função de callback ou por uma Promise que é resolvida ou rejeitada. Ex: fs.readFile(), fs.writeFile().
  2. Síncrona (bloqueante): Estas funções executam a operação e só retornam após a conclusão, bloqueando o event loop do Node.js. Devem ser usadas com cautela e apenas em casos muito específicos, como na inicialização de um aplicativo onde o bloqueio não é um problema. Ex: fs.readFileSync(), fs.writeFileSync().

Nesta aula, vamos focar principalmente na abordagem assíncrona com Promises, que é a forma mais moderna e limpa de lidar com operações assíncronas no Node.js, especialmente a partir do Node.js 10+.

🌟 fs.promises: A Abordagem Moderna

A API fs.promises oferece todas as funcionalidades do módulo fs encapsuladas em Promises, permitindo o uso de async/await para um código mais legível e fácil de manter.

Para usar a API de Promises, você pode importá-la assim:

import * as fs from 'node:fs/promises'; // ES Modules
// ou
// const fs = require('node:fs/promises'); // CommonJS

Vamos explorar algumas das funções mais comuns com exemplos práticos.

1. Lendo Arquivos 📖 (fs.promises.readFile())

Para ler o conteúdo de um arquivo.

Exemplo da Documentação Oficial (adaptado):

// arquivo: lerArquivo.js
import * as fs from 'node:fs/promises';
 
async function lerConteudoArquivo(caminhoArquivo) {
  try {
    const data = await fs.readFile(caminhoArquivo, { encoding: 'utf8' });
    console.log('Conteúdo do arquivo:', data);
  } catch (err) {
    console.error('Erro ao ler o arquivo:', err);
  }
}
 
// Crie um arquivo de exemplo para testar:
// echo "Olá, mundo do Node.js!" > exemplo.txt
lerConteudoArquivo('exemplo.txt');
 
// Tentando ler um arquivo que não existe:
// lerConteudoArquivo('arquivo-nao-existe.txt');

Explicação:

  • fs.readFile(caminho, opções): Lê o arquivo especificado.
  • { encoding: 'utf8' }: Garante que o conteúdo seja lido como uma string UTF-8. Sem isso, você receberia um Buffer.
  • Usamos async/await para tratar a Promise retornada por readFile.
  • O bloco try...catch é essencial para lidar com erros, como o arquivo não ser encontrado.

2. Escrevendo Arquivos 📝 (fs.promises.writeFile())

Para escrever dados em um arquivo. Se o arquivo não existir, ele será criado. Se existir, seu conteúdo será sobrescrito por padrão.

Exemplo da Documentação Oficial (adaptado):

// arquivo: escreverArquivo.js
import * as fs from 'node:fs/promises';
 
async function escreverEmArquivo(caminhoArquivo, conteudo) {
  try {
    await fs.writeFile(caminhoArquivo, conteudo);
    console.log('Arquivo escrito com sucesso!');
  } catch (err) {
    console.error('Erro ao escrever no arquivo:', err);
  }
}
 
escreverEmArquivo('meuArquivo.txt', 'Este é o conteúdo que estou escrevendo no arquivo.\nMais uma linha.');
escreverEmArquivo('dados.json', JSON.stringify({ nome: 'Alice', idade: 30 }, null, 2));

Explicação:

  • fs.writeFile(caminho, dados, opções): Escreve os dados no caminho especificado.
  • Os dados podem ser uma string, um Buffer, ou um Uint8Array.
  • No exemplo do JSON, JSON.stringify(..., null, 2) formata o JSON com indentação de 2 espaços, tornando-o mais legível.

3. Anexando Conteúdo a Arquivos ➕ (fs.promises.appendFile())

Para adicionar conteúdo ao final de um arquivo existente sem sobrescrevê-lo. Se o arquivo não existir, ele será criado.

Exemplo da Documentação Oficial (adaptado):

// arquivo: anexarArquivo.js
import * as fs from 'node:fs/promises';
 
async function anexarConteudo(caminhoArquivo, conteudo) {
  try {
    await fs.appendFile(caminhoArquivo, conteudo);
    console.log('Conteúdo anexado com sucesso!');
  } catch (err) {
    console.error('Erro ao anexar conteúdo ao arquivo:', err);
  }
}
 
// Primeiro, crie ou sobrescreva o arquivo
await fs.writeFile('log.txt', 'Início do log:\n');
console.log('Arquivo log.txt inicializado.');
 
// Agora, anexe conteúdo
anexarConteudo('log.txt', 'Usuário logou em: ' + new Date().toISOString() + '\n');
setTimeout(() => {
  anexarConteudo('log.txt', 'Usuário deslogou em: ' + new Date().toISOString() + '\n');
}, 1000); // Anexa após 1 segundo

Explicação:

  • fs.appendFile(caminho, dados, opções): Adiciona os dados ao final do arquivo.
  • É útil para arquivos de log ou para adicionar informações incrementalmente.

4. Deletando Arquivos 🗑️ (fs.promises.unlink())

Para remover um arquivo do sistema de arquivos.

Exemplo da Documentação Oficial (adaptado):

// arquivo: deletarArquivo.js
import * as fs from 'node:fs/promises';
 
async function deletarArquivo(caminhoArquivo) {
  try {
    await fs.unlink(caminhoArquivo);
    console.log(`Arquivo '${caminhoArquivo}' deletado com sucesso!`);
  } catch (err) {
    if (err.code === 'ENOENT') {
      console.warn(`Aviso: O arquivo '${caminhoArquivo}' não existe.`);
    } else {
      console.error('Erro ao deletar o arquivo:', err);
    }
  }
}
 
// Crie um arquivo para deletar
await fs.writeFile('arquivoParaDeletar.txt', 'Este arquivo será deletado.');
console.log('Arquivo "arquivoParaDeletar.txt" criado.');
 
// Deleta o arquivo
deletarArquivo('arquivoParaDeletar.txt');
 
// Tentando deletar um arquivo que já não existe
// deletarArquivo('arquivoQueNaoExisteMais.txt');

Explicação:

  • fs.unlink(caminho): Remove o arquivo.
  • O erro ENOENT (Error NO ENTry) significa que o arquivo ou diretório não existe. É uma boa prática tratá-lo.

5. Manipulando Diretórios 📂

O módulo fs também permite criar, ler e excluir diretórios.

Criando Diretórios (fs.promises.mkdir())

// arquivo: criarDiretorio.js
import * as fs from 'node:fs/promises';
 
async function criarDiretorio(caminhoDiretorio) {
  try {
    await fs.mkdir(caminhoDiretorio, { recursive: true });
    console.log(`Diretório '${caminhoDiretorio}' criado com sucesso!`);
  } catch (err) {
    console.error('Erro ao criar diretório:', err);
  }
}
 
criarDiretorio('minhaPasta/subPasta'); // Cria "minhaPasta" e "subPasta" dentro dela
criarDiretorio('uploads');

Explicação:

  • fs.mkdir(caminho, opções): Cria um diretório.
  • { recursive: true }: Esta opção é crucial! Se true, ele criará todos os diretórios pai necessários caso não existam. Se false (padrão) e um diretório pai não existir, um erro será lançado.

Lendo Conteúdo de Diretórios (fs.promises.readdir())

Para listar os arquivos e subdiretórios dentro de um diretório.

// arquivo: lerDiretorio.js
import * as fs from 'node:fs/promises';
 
async function listarConteudoDiretorio(caminhoDiretorio) {
  try {
    const arquivos = await fs.readdir(caminhoDiretorio);
    console.log(`Conteúdo do diretório '${caminhoDiretorio}':`);
    for (const arquivo of arquivos) {
      console.log(`- ${arquivo}`);
    }
  } catch (err) {
    console.error('Erro ao ler o diretório:', err);
  }
}
 
// Certifique-se de que a pasta 'minhaPasta' exista ou crie-a antes de rodar
// await fs.mkdir('minhaPasta', { recursive: true });
// await fs.writeFile('minhaPasta/arquivo1.txt', 'Conteúdo 1');
// await fs.writeFile('minhaPasta/arquivo2.js', 'console.log("Olá");');
 
listarConteudoDiretorio('minhaPasta');
listarConteudoDiretorio('.'); // Lista o diretório atual

Explicação:

  • fs.readdir(caminho): Retorna um array de strings com os nomes dos arquivos e diretórios dentro do caminho especificado.

Deletando Diretórios 💥 (fs.promises.rm())

Para remover diretórios. A partir do Node.js 14, fs.promises.rm() é a forma recomendada, substituindo fs.promises.rmdir() para a maioria dos casos, pois rm pode remover arquivos e diretórios recursivamente.

// arquivo: deletarDiretorio.js
import * as fs from 'node:fs/promises';
 
async function deletarDiretorio(caminhoDiretorio) {
  try {
    // Crie um diretório e alguns arquivos dentro dele para testar
    await fs.mkdir(caminhoDiretorio + '/sub', { recursive: true });
    await fs.writeFile(caminhoDiretorio + '/sub/teste.txt', 'Olá');
    console.log(`Diretório e arquivo de teste criados em '${caminhoDiretorio}'.`);
 
    await fs.rm(caminhoDiretorio, { recursive: true, force: true });
    console.log(`Diretório '${caminhoDiretorio}' e seu conteúdo deletados com sucesso!`);
  } catch (err) {
    if (err.code === 'ENOENT') {
      console.warn(`Aviso: O diretório '${caminhoDiretorio}' não existe.`);
    } else {
      console.error('Erro ao deletar o diretório:', err);
    }
  }
}
 
deletarDiretorio('minhaPastaParaDeletar');

Explicação:

  • fs.rm(caminho, opções): Remove arquivos ou diretórios.
  • { recursive: true }: Essencial para remover diretórios não vazios.
  • { force: true }: Ignora erros se o caminho não existir (útil para garantir que o diretório não exista, mesmo que já tenha sido removido).

6. Obtendo Informações de Arquivos/Diretórios ℹ️ (fs.promises.stat())

Para obter metadados sobre um arquivo ou diretório (tamanho, data de modificação, se é um arquivo ou diretório, etc.).

// arquivo: obterInfo.js
import * as fs from 'node:fs/promises';
 
async function obterInformacoes(caminho) {
  try {
    const stats = await fs.stat(caminho);
 
    console.log(`Informações para: '${caminho}'`);
    console.log(`- É um arquivo? ${stats.isFile()}`);
    console.log(`- É um diretório? ${stats.isDirectory()}`);
    console.log(`- Tamanho: ${stats.size} bytes`);
    console.log(`- Data de criação: ${stats.birthtime}`);
    console.log(`- Data da última modificação: ${stats.mtime}`);
  } catch (err) {
    if (err.code === 'ENOENT') {
      console.error(`Erro: '${caminho}' não encontrado.`);
    } else {
      console.error('Erro ao obter informações:', err);
    }
  }
}
 
// Crie um arquivo de exemplo para testar
await fs.writeFile('infoTeste.txt', 'Conteúdo para teste de info.');
await fs.mkdir('pastaInfo', { recursive: true });
 
obterInformacoes('infoTeste.txt');
obterInformacoes('pastaInfo');
obterInformacoes('arquivo-que-nao-existe.txt');

Explicação:

  • fs.stat(caminho): Retorna um objeto fs.Stats com várias propriedades e métodos para verificar o tipo e os metadados do item.
  • stats.isFile(), stats.isDirectory(): Métodos úteis para verificar o tipo do item.

🤝 Integração com Múltiplas Tecnologias (Contexto)

Embora o módulo fs seja uma tecnologia core do Node.js e não se "integre" diretamente com outras bibliotecas da mesma forma que um middleware do Express, ele é fundamental para quase todo aplicativo Node.js que precisa interagir com o ambiente local.

Exemplo: Um servidor web (como Express) pode usar fs para:

  • Servir arquivos estáticos: Ler arquivos HTML, CSS, JS, imagens.
  • Carregar configurações: Ler um arquivo config.json ou .env.
  • Fazer upload de arquivos: Salvar arquivos enviados por usuários.
  • Geração de logs: Escrever eventos em arquivos de log.

Vamos ver um exemplo simples de como o fs poderia ser usado para ler um arquivo de configuração JSON em uma aplicação Node.js:

// arquivo: app.js
import * as fs from 'node:fs/promises';
 
// Simulando um arquivo de configuração
// Crie um arquivo 'config.json' com o conteúdo:
// { "porta": 3000, "ambiente": "desenvolvimento" }
 
async function iniciarAplicacao() {
  let config = {};
  try {
    const configData = await fs.readFile('config.json', { encoding: 'utf8' });
    config = JSON.parse(configData);
    console.log('Configurações carregadas:', config);
  } catch (err) {
    console.error('Erro ao carregar config.json:', err);
    console.log('Usando configurações padrão.');
    config = { porta: 8080, ambiente: 'producao' }; // Configurações padrão
  }
 
  console.log(`Aplicação iniciada na porta ${config.porta} em ambiente de ${config.ambiente}.`);
  // Aqui você continuaria com a lógica da sua aplicação,
  // por exemplo, iniciar um servidor Express usando config.porta
  // const express = require('express');
  // const app = express();
  // app.listen(config.porta, () => console.log('Servidor rodando...'));
}
 
iniciarAplicacao();

Este exemplo mostra como o fs é usado para carregar dados essenciais para a inicialização da aplicação, demonstrando sua utilidade em um contexto maior.

🏋️ Exercícios/Desafios Práticos

Agora é a sua vez de colocar a mão na massa! Crie um novo diretório para esta aula e execute os desafios abaixo. Lembre-se de usar a API fs.promises e async/await para todos os exercícios.

Desafio 1: Gerenciador de Notas Simples 📝

Crie um conjunto de funções para gerenciar notas em arquivos de texto.

Tarefas:

  • Crie um arquivo chamado gerenciadorNotas.js.
  • Função criarNota(titulo, conteudo):
    • Deve criar um novo arquivo .txt com o titulo como nome (ex: minha-nota.txt).
    • O conteudo deve ser escrito dentro do arquivo.
    • Exiba uma mensagem de sucesso ou erro.
  • Função lerNota(titulo):
    • Deve ler o conteúdo do arquivo .txt correspondente ao titulo.
    • Exiba o conteúdo da nota no console.
    • Trate o caso em que a nota não existe.
  • Função listarNotas():
    • Deve listar todos os arquivos .txt no diretório atual (ou em um subdiretório notas/).
    • Exiba os títulos das notas encontradas.
  • Função deletarNota(titulo):
    • Deve remover o arquivo .txt correspondente ao titulo.
    • Exiba uma mensagem de sucesso ou erro.
  • Função atualizarNota(titulo, novoConteudo):
    • Deve sobrescrever o conteúdo de uma nota existente com novoConteudo.
    • Trate o caso em que a nota não existe.

Dicas:

  • Use path.join() do módulo path para construir caminhos de arquivo de forma segura, evitando problemas com barras em diferentes sistemas operacionais.
  • Considere criar um diretório notas/ para armazenar todas as notas e manter a organização.

Desafio 2: Analisador de Logs 📊

Crie um script que simula a escrita de logs e, em seguida, os analisa.

Tarefas:

  • Crie um arquivo chamado analisadorLogs.js.
  • Parte 1: Geração de Logs
    • Crie uma função gerarLog(mensagem) que anexa uma linha de log ao arquivo app.log.
    • Cada linha de log deve incluir um timestamp (ex: [2023-10-27 10:30:00] Mensagem de log).
    • Gere 5-10 logs aleatórios com diferentes mensagens (ex: "Usuário X logou", "Erro no banco de dados", "Requisição concluída").
  • Parte 2: Análise de Logs
    • Crie uma função analisarLogs() que lê o arquivo app.log.
    • Conte quantas ocorrências de "Erro" existem no arquivo.
    • Exiba o número total de logs e o número de logs de erro.
    • Exiba a primeira e a última linha de log.

Dicas:

  • Use new Date().toISOString() para o timestamp.
  • Use String.prototype.includes() para verificar se uma linha de log contém "Erro".
  • Lembre-se de tratar a criação do arquivo app.log se ele não existir.

💡 Resumo e Próximos Passos

Nesta aula, exploramos o poderoso módulo fs do Node.js, focando na API fs.promises para operações assíncronas no sistema de arquivos. Você aprendeu a:

  • Ler e escrever arquivos (readFile, writeFile).
  • Anexar conteúdo a arquivos (appendFile).
  • Deletar arquivos e diretórios (unlink, rm).
  • Criar e listar diretórios (mkdir, readdir).
  • Obter informações sobre arquivos e diretórios (stat).
  • Apreciar a importância do tratamento de erros em operações de I/O.

O módulo fs é a base para muitas operações essenciais em aplicações Node.js. Dominá-lo é um passo crucial para se tornar um desenvolvedor Node.js completo!

Próximos Passos:

  • Explore mais métodos fs: A documentação oficial do Node.js para fs é vasta. Explore métodos como rename(), watch(), access(), copyFile(), entre outros.
  • Streams: Para trabalhar com arquivos muito grandes, o fs oferece a API de Streams (fs.createReadStream(), fs.createWriteStream()). Streams são mais eficientes em termos de memória.
  • Módulo path: Complementar ao fs, o módulo path ajuda a manipular e normalizar caminhos de arquivo e diretório de forma independente do sistema operacional.
  • Integração com Express/APIs: Pense em como você usaria o fs em uma API REST para servir arquivos, gerenciar uploads ou armazenar dados simples.

Continue praticando e experimentando! O sistema de arquivos é um recurso fundamental, e saber como interagir com ele de forma eficiente e segura é uma habilidade valiosa. Bons estudos! 🚀

© 2025 Escola All Dev. Todos os direitos reservados.