Working with file systems is an important part of a developer's job. This includes creating files and directories, deleting them, reading and writing to/from files, and sometimes keeping track of all the processes that happen to files and directories. This is where file system watchers come in handy. In this article, you will learn about fs watchers in Node.js and how you can use it to monitor changes in the file system.
What is a watcher
The fs watcher in Node.js is a module that allows you to monitor changes in the file system. It watches a given directory or file and emits events when changes occur — when a file is created, updated, or deleted. This allows you to automate processes related to changes in the file system, like reloading a web page when someone edits a file. The fs module in Node.js provides several methods to implement a watcher. Two of the methods are fs.watch() and fs.watchFile().
Before explaining the two methods in more detail. First, bring the fs module to your code. You can do it like this:
const fs = require('node:fs');
Or you can destructure necessary methods by wrapping them in curly braces:
const { watch, watchFile } = require('node:fs');
Let's look at the watch methods in action.
Watch method
The fs.watch() method lets you monitor a specified file or directory for changes. The syntax of the method is:
fs.watch(filename[, options], callback)
filename— a mandatory parameter, which is a filepathto watchoptions— an optional parameter that lets you set additional settings like values forpersistent,recursive,encoding, andsignalpropertiescallback— fires off on every file/directory change and takes the following two parameters:eventType— "rename", "change"filename— a file or directory that was subject to some kind of modification
You can read more about these parameters in the official Node.js Docs.
Let's create a friends folder with two files in it. You may name the files by your friends' names. The following piece of code watches for changes in these files:
const fs = require('node:fs');
fs.watch('friends', (eventType, filename) => {
console.log(`Event type: ${eventType}`);
console.log(`File name: ${filename}`);
});
This snippet contains a path to a specified folder to watch — friends. In the callback, the program prints the event type and the name of the file that was affected. Try to run your file with this code and then write something to one of the files. You will see some messages in your console. For example, you can write something in Emily.txt file and here's what the program will output for the file:
Event type: change
File name: Emily.txt
Play around with the program by trying to type something, rename, or move the file to another folder. On some computers, you may notice that the event fires a few times for a single change. This isn't what we want, and it might be confusing, because we may assume that the file changed a few times, when in fact it was just a one-time change. But this is how Node.js works on certain machines. If you're curious why that happens, you may read some explanations in Microsoft Docs (scroll down to the purplish "Note").
There are a couple workarounds to this issue, as the simplest one you can use state. You will set it to true when the file is changed, then wait for some time using setTimeout() (wait for duplicate events to fire). Finally, revert state back to false so that the program can continue listening for the next events. When state is set to true, you will stop the program by writing a return statement, but when it is false, you will resume to receive notifications from the program. This is how it looks like:
let watching = false;
fs.watch('friends', (eventType, filename) => {
if (watching) return;
watching = true;
console.log(`Event type: ${eventType}`);
console.log(`File name: ${filename}`);
setTimeout(() => {
watching = false;
}, 100);
});
Let's say you have a folder called friends which in turn has multiple folders. And what if you want the program to notify you about changes in each of the folders and files inside of it? Well, you just need to provide the second optional parameter called recursive and set it to true. This is what it looks like:
fs.watch('friends', {recursive: true}, (eventType, filename) => {
console.log(`Event type: ${eventType}`);
console.log(`File name: ${filename}`);
});WatchFile method
Unlike the fs.watch() method that watches for both directories and files, the fs.watchFile() only allows you to monitor files. The syntax for the WatchFile method is:
fs.watchFile(filename[, options], callback)
filename— a mandatory parameter, which is a filepathto watchoptions— an optional parameter that lets you set additional settings like values forbigint,persistent, andintervalpropertiescallback— accepts thecurrentandpreviousparameters. Those two are the objects of fs.stat type. Basically, thefs.stat()method returns information about a specific file, such as file size, creation date, modification date, etc. This info is useful if you want to see how a file changes. You can compare the size of thepreviousandcurrentstates to see if there was a change.
Here's a code snippet that utilizes the fs.watchFile() method:
const fs = require('node:fs');
fs.watchFile('friends/Emily.txt', (curr, prev) => {
console.log(`The file was last modified at — ${prev.mtime}.`);
console.log(`The previous size of the file is ${prev.size} bytes.`);
console.log(`The current size of the file is ${curr.size} bytes.`);
});
In this example, the program is watching the Emily.txt file. It prints to the console the time when the file was last modified along with the current and previous sizes of the file. Adding a single character to the file after running the code above gives the following result:
The file was last modified at Tue Jun 13 2023 16:30:09 GMT+0300 (Moscow Standard Time).
The previous size of the file is 16 bytes.
The current size of the file is 17 bytes.
Did you notice that there is some kind of delay before the console prints out the results? This is because the fs.watchFile() method checks on the changes in the file every 5007 milliseconds (around 5 seconds) by default. But you can change that by providing an additional optional parameter called interval. The snippet below uses this parameter and sets the delay to 1000 milliseconds (1 second):
const fs = require('node:fs');
fs.watchFile('friends/Emily.txt', {interval: 1000}, (curr, prev) => {
if (curr.size > 5000) {
console.log('File is too big!');
};
console.log(`The file was modified. Current size is ${curr.size} bytes.`);
});
In this example, the code is watching for the same Emily.txt file at an interval of one second. If the size exceeds the 5000 bytes limit, the program will inform that the file is too big. Otherwise, it will print the current size. Try to run this code. You'll notice that messages appear faster in the console thanks to the interval property.
Conclusion
In this topic, we discussed fs watchers in Node.js and how they can be used to monitor changes in the file system. The two main methods for watching in Node.js are fs.watch() and fs.watchFile(). The former method can be used to watch both a file and a directory, whereas the latter is only suitable for tracking changes in files. The main difference in the syntax of these methods is the parameters that the callback has: fs.watch takes an eventType and filename, whereas fs.watchFile takes a curr and prev states.