fs.watch no Node.js monitora alterações em arquivos e diretórios usando os mecanismos oferecidos pelo sistema operacional. Ele é útil para recarregar configuração, observar diretórios de entrada, implementar ferramentas de desenvolvimento e iniciar pipelines após novos arquivos.
A API não é um log durável de mudanças. Eventos podem ser agrupados, duplicados ou perdidos, e o comportamento varia entre Linux, macOS e Windows. Por isso, o watcher deve servir como sinal para uma nova verificação do estado real.
Exemplo básico
import { watch } from 'node:fs';
const watcher = watch('./config', (eventType, filename) => {
console.log({ eventType, filename });
});
watcher.on('error', error => {
console.error('watch error', error);
});eventType costuma ser rename ou change. O nome pode ser ausente em algumas plataformas.
Promises e iterador assíncrono
import { watch } from 'node:fs/promises';
const controller = new AbortController();
for await (const event of watch('./inbox', {
signal: controller.signal
})) {
console.log(event.eventType, event.filename);
}O iterador facilita cancelamento e composição com loops assíncronos.
Eventos são pistas
Não assuma que cada evento corresponde a exatamente uma alteração. Ao receber sinal, faça stat, liste o diretório ou releia o arquivo:
async function reconcileDirectory() {
const entries = await readdir('./inbox', { withFileTypes: true });
return entries.filter(entry => entry.isFile());
}Debounce
Editores frequentemente salvam por arquivo temporário e rename, gerando vários eventos. Agrupe mudanças próximas:
let timer;
function scheduleReconcile() {
clearTimeout(timer);
timer = setTimeout(() => {
reconcileDirectory().catch(console.error);
}, 100);
}O intervalo deve equilibrar latência e quantidade de leituras.
Arquivos gravados parcialmente
Um produtor pode emitir evento enquanto ainda grava. Prefira protocolo de arquivo temporário seguido de rename atômico. O consumidor processa somente extensões finais.
Deduplicação
Registre identidade, tamanho, mtime ou checksum para evitar reprocessar o mesmo arquivo. Para efeitos externos, use idempotência durável.
Watch recursivo
O suporte a recursive varia por plataforma e versão. Teste no ambiente de produção. Em Linux, pode ser necessário criar watchers por diretório ou usar biblioteca especializada.
Limites do sistema
Muitos watchers consomem descritores e recursos do kernel. No Linux, limites do inotify podem ser atingidos. Monitore erros e evite observar árvores enormes sem necessidade.
Symlinks
Defina política para links simbólicos. Um diretório observado pode apontar para fora da raiz esperada. Use caminhos canônicos e verifique containment antes de processar arquivos.
Renames
Um rename pode representar criação, exclusão ou substituição. Releia o estado real:
try {
const info = await stat(pathname);
if (info.isFile()) await processFile(pathname);
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}Rotação de arquivos
Logs e configurações podem ser substituídos por rename. Um watcher preso ao inode antigo pode deixar de acompanhar o novo arquivo em algumas plataformas. Observar o diretório pai costuma ser mais robusto.
Rede e volumes montados
NFS, SMB, Docker bind mounts e volumes remotos podem não entregar eventos de forma confiável. Use polling ou reconciliação periódica quando correção for importante.
fs.watchFile
fs.watchFile usa polling de estatísticas. Consome mais recursos, mas pode funcionar em ambientes onde eventos nativos são inconsistentes:
import { watchFile, unwatchFile } from 'node:fs';
watchFile('./config.json', { interval: 1000 }, (current, previous) => {
if (current.mtimeMs !== previous.mtimeMs) reload();
});Backpressure
Se o processamento é mais lento que a chegada de arquivos, use fila limitada. Não inicie trabalho ilimitado no callback.
Checkpoint
Para diretórios de entrada, mova arquivos processados para outra pasta ou registre status no banco. Depois de reiniciar, reconcilie todos os arquivos pendentes.
Segurança
- não confie no nome do arquivo;
- bloqueie path traversal;
- verifique tamanho e tipo;
- não siga symlinks sem política;
- processe com permissões mínimas;
- quarentene conteúdo não confiável.
Configuração dinâmica
Ao recarregar configuração, valide completamente antes de substituir a versão ativa:
const next = validateConfig(JSON.parse(await readFile(file, 'utf8')));
activeConfig = Object.freeze(next);Se o arquivo estiver inválido, mantenha a configuração anterior e emita alerta.
Shutdown
process.once('SIGTERM', () => {
watcher.close();
clearTimeout(timer);
});Com iterador assíncrono, aborte o controller e aguarde o loop encerrar.
Observabilidade
Monitore eventos recebidos, reconciliações, duração, fila pendente, arquivos ignorados, erros, reinícios do watcher e tempo desde o último evento.
Testes
- criação;
- alteração rápida;
- rename atômico;
- exclusão;
- filename ausente;
- evento duplicado;
- arquivo parcial;
- symlink;
- reinício e reconciliação;
- shutdown.
Erros comuns
- tratar eventos como histórico completo;
- processar imediatamente sem debounce;
- não reler o estado;
- concorrência ilimitada;
- não ter reconciliação periódica;
- ignorar diferenças de plataforma;
- observar arquivo que é substituído por rename;
- confiar em volumes de rede.
Conclusão
fs.watch no Node.js é adequado como mecanismo de notificação, não como fonte de verdade. O código deve transformar eventos em gatilhos para verificar novamente arquivos e diretórios.
Use debounce, idempotência, fila limitada e reconciliação. Para fluxos críticos, registre estado durável e garanta que uma reinicialização descubra tudo que ainda precisa ser processado.
Consulte a documentação oficial de fs.watch e a documentação de fsPromises.watch.




