Ao terminar esta aula, você vai conseguir
- Usar as APIs assíncronas de leitura e escrita
- Resolver caminhos a partir do próprio módulo
- Tratar falhas conhecidas sem esconder erros inesperados
Persistir significa manter dados além da execução atual. Para este primeiro
projeto, um arquivo JSON será nossa persistência. Node.js fornece node:fs para
interagir com o sistema de arquivos e node:path para montar caminhos sem
assumir separadores específicos do sistema operacional.
Pense num arquivo como um caderno compartilhado pelo programa: antes de anotar, você abre na página certa; depois registra e fecha. A comparação ajuda a lembrar que leitura e escrita podem demorar. O limite é que um arquivo não é banco de dados: duas escritas simultâneas, volume grande e múltiplos processos exigem coordenação que este exemplo não oferece.
Resolva o caminho a partir do módulo
O diretório atual pode mudar conforme o lugar de onde o comando foi chamado. Em ES Modules, derive o caminho do próprio arquivo:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const arquivoAtual = fileURLToPath(import.meta.url);
const diretorioAtual = path.dirname(arquivoAtual);
const caminhoDados = path.join(diretorioAtual, '..', 'dados', 'tarefas.json');import.meta.url identifica o módulo como URL; fileURLToPath converte essa URL
em caminho do sistema; path.dirname encontra a pasta; path.join combina os
segmentos. Isso é mais explícito e portável que escrever barras manualmente.
Crie dados/tarefas.json com uma lista inicial:
[
{ "id": 1, "titulo": "Aprender fs", "concluida": false }
]Leia, interprete e valide
Use a variante de Promises para não bloquear a thread enquanto o sistema operacional acessa o disco:
import { readFile } from 'node:fs/promises';
export async function listarTarefas() {
const texto = await readFile(caminhoDados, 'utf8');
const dados = JSON.parse(texto);
if (!Array.isArray(dados)) {
throw new Error('O arquivo de tarefas precisa conter um array');
}
return dados;
}readFile entrega texto porque passamos a codificação utf8. JSON.parse
transforma esse texto em valor JavaScript e pode lançar erro se a sintaxe estiver
quebrada. Depois, a validação confirma a forma mínima esperada. JSON válido não
garante dados adequados ao domínio.
Grave sem perder a serialização
Para adicionar uma tarefa, leia o estado atual, crie um novo array e grave:
import { writeFile } from 'node:fs/promises';
export async function adicionarTarefa(titulo) {
const tarefas = await listarTarefas();
const nova = { id: Date.now(), titulo: titulo.trim(), concluida: false };
const atualizadas = [...tarefas, nova];
await writeFile(caminhoDados, JSON.stringify(atualizadas, null, 2), 'utf8');
return nova;
}O terceiro argumento do JSON.stringify deixa o arquivo legível. Isso não torna
a escrita transacional: uma aplicação real pode preferir banco de dados ou uma
estratégia de arquivo temporário e renomeação.
Crie a pasta dados conscientemente antes da primeira gravação. writeFile pode
criar o arquivo, mas não cria todos os diretórios ausentes do caminho. Também
decida quem é responsável pelo valor inicial: o projeto pode versionar um []
ou o repositório pode tratar apenas ENOENT como primeira execução e criar a
coleção. Registrar essa decisão evita que cada chamada invente um comportamento.
Depois de gravar, leia novamente e rode a validação. Essa verificação custa mais e não é necessária em toda operação real, mas é uma microprática útil nesta aula: ela comprova que serialização, caminho e codificação concordam entre si.
Ao relatar uma falha, acrescente contexto que ajude sem expor conteúdo sensível: qual operação falhou e qual arquivo lógico estava envolvido. Preserve a causa original para investigação. Uma mensagem como “não foi possível carregar tarefas” orienta melhor que “deu erro”, mas não precisa imprimir todo o conteúdo do arquivo nem dados pessoais no log.
O laboratório trabalha apenas com validação e serialização em memória. O
sandbox do navegador não acessa seu disco, portanto a leitura e a gravação devem
ser praticadas no projeto Node. Quebre o id de propósito e confirme a mensagem
antes de restaurar o valor.
Erro comum: capturar tudo e devolver lista vazia
Um catch que retorna [] para qualquer falha esconde JSON corrompido e erro de
permissão como se fossem “nenhuma tarefa”. Trate somente situações previstas,
como um arquivo ainda inexistente identificado por erro.code === 'ENOENT', e
propague o restante. Registrar contexto também ajuda a investigar a causa.
Na missão, rode a adição duas vezes e abra o arquivo. A conclusão exige JSON válido, três tarefas preservadas e nenhuma sobrescrita acidental do histórico. Na última aula, vamos ligar módulos, HTTP, validação e persistência numa API.
Laboratório ao vivo
Validação antes da persistência
O sandbox não acessa seu disco. Edite os dados e execute para validar a estrutura que seria serializada antes de uma gravação feita pelo Node.js.
Pare e pense
Por que usar node:path em vez de concatenar barras manualmente?
node:path conhece as regras de caminhos do ambiente e oferece funções como join e resolve. Ele não lê arquivos nem concede permissões.
Faça sem copiar
Crie dados/tarefas.json, leia-o com fs/promises e grave uma nova tarefa. Rode duas vezes e confirme que o JSON continua válido e contém os itens anteriores.
Fontes para consultar
Terminou a missão?
Marque apenas quando você conseguir explicar o conceito e concluir o desafio. O progresso fica salvo somente neste navegador.
Próxima: Projeto Node.js: API de tarefas do começo ao fim →