Fundamentos do Node.js
Módulo 'fs': Trabalhando com o Sistema de Arquivos
Aprenda sobre módulo 'fs': trabalhando com o sistema de arquivos
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:
- 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(). - 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'); // CommonJSVamos 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 umBuffer.- Usamos
async/awaitpara tratar a Promise retornada porreadFile. - 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 osdadosnocaminhoespecificado.- Os
dadospodem ser uma string, um Buffer, ou umUint8Array. - 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 segundoExplicação:
fs.appendFile(caminho, dados, opções): Adiciona osdadosao 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! Setrue, ele criará todos os diretórios pai necessários caso não existam. Sefalse(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 atualExplicaçã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 objetofs.Statscom 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.jsonou.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
.txtcom otitulocomo nome (ex:minha-nota.txt). - O
conteudodeve ser escrito dentro do arquivo. - Exiba uma mensagem de sucesso ou erro.
- Deve criar um novo arquivo
- Função
lerNota(titulo):- Deve ler o conteúdo do arquivo
.txtcorrespondente aotitulo. - Exiba o conteúdo da nota no console.
- Trate o caso em que a nota não existe.
- Deve ler o conteúdo do arquivo
- Função
listarNotas():- Deve listar todos os arquivos
.txtno diretório atual (ou em um subdiretórionotas/). - Exiba os títulos das notas encontradas.
- Deve listar todos os arquivos
- Função
deletarNota(titulo):- Deve remover o arquivo
.txtcorrespondente aotitulo. - Exiba uma mensagem de sucesso ou erro.
- Deve remover o arquivo
- 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.
- Deve sobrescrever o conteúdo de uma nota existente com
Dicas:
- Use
path.join()do módulopathpara 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 arquivoapp.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").
- Crie uma função
- Parte 2: Análise de Logs
- Crie uma função
analisarLogs()que lê o arquivoapp.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.
- Crie uma função
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.logse 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 parafsé vasta. Explore métodos comorename(),watch(),access(),copyFile(), entre outros. - Streams: Para trabalhar com arquivos muito grandes, o
fsoferece a API de Streams (fs.createReadStream(),fs.createWriteStream()). Streams são mais eficientes em termos de memória. - Módulo
path: Complementar aofs, o módulopathajuda 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
fsem 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! 🚀