Accessing the File System in Node.js

Colin Ihrig
Share

For years, JavaScript has had very limited access to the file system. Of course, for most of its life, JavaScript lived in the browser. For a web scripting language, accessing the file system was considered a major security risk. Front end developers have been forced to make due with cookies, web storage, ActiveX, Flash, and other technologies. HTML5 brought about the file system API, but it remains largely unsupported outside of Chrome. With the advent of Node.js, JavaScript began to gain footing as a legitimate server-side language. On the server, file system accesses are a regular occurrence, making the thought of an API much less concerning.

The File System Module

By default, Node.js installations come with the file system module, fs. For the most part, fs simply provides a wrapper for the standard file operations. The following example uses the file system module to read the contents of a file into memory. First, the file system module is imported on line 1. On line 3, the exists() function determines if the file “foo.txt” exists. The exists() callback function takes a Boolean argument which specifies if the file exists or not. From there, the stat() function is used to determine the length of the file in bytes. This information is important so we know how much data to read.

Next, we open the file using open(). The r argument denotes that the file is being opened for reading. The open() callback function provides a file descriptor, fd for accessing the newly opened file. Inside the callback function, we define a buffer to hold the file’s contents. Notice that the buffer is initialized to the file’s size, which is stored in stats.size. Next, the file is read into the buffer using the read() function. The buffer now contains the raw data read from the file. In order to display the data, we must first convert it to a UTF-8 encoded string. Finally, the file contents are printed to the console, and the file is closed.

var fs = require("fs");
var fileName = "foo.txt";

fs.exists(fileName, function(exists) {
  if (exists) {
    fs.stat(fileName, function(error, stats) {
      fs.open(fileName, "r", function(error, fd) {
        var buffer = new Buffer(stats.size);

        fs.read(fd, buffer, 0, buffer.length, null, function(error, bytesRead, buffer) {
          var data = buffer.toString("utf8", 0, buffer.length);

          console.log(data);
          fs.close(fd);
        });
      });
    });
  }
});

Synchronous Functions

While looking over the documentation, you may notice that many of the functions end with Sync. These represent synchronous functions – a bit of a rarity in the callback driven world of Node.js. The synchronous functions are provided for convenience. For example, a simple batch script written in Node.js probably doesn’t need to worry about maximizing performance. The synchronous functions are also useful for loading certain files during program initialization. However, in an active server application, the synchronous functions have the potential to seriously hamper performance by stalling Node’s single thread of execution.

The following examples show how a file can be read into memory both synchronously and asynchronously. Our previous example of reading a file is a bit convoluted, to say the least. This time, our example uses the readFile() function to read an entire file in a single function call. The first two arguments to readFile() are the filename and its encoding. Following Node.js convention, the final argument is the callback function. The callback function’s arguments provide error information and the file contents.

var fs = require("fs");

fs.readFile("foo.txt", "utf8", function(error, data) {
  console.log(data);
});

The following example performs the same task synchronously using readFileSync(). The synchronous code is slightly more readable, but does not offer the same scalability as its asynchronous counterpart.

var fs = require("fs");
var data = fs.readFileSync("foo.txt", "utf8");

console.log(data);

Watching Files

The file system module allows programs to watch for modifications to specific files. This is very useful in programs such as nodemon, which automatically restarts a program when its source code is modified. The following example watches a file named “foo.txt”. When the file is modified, the type of event is printed to the console.

var fs = require("fs");
var fileName = "foo.txt";

fs.watch(fileName, {
  persistent: true
}, function(event, filename) {
  console.log(event + " event occurred on " + filename);
});

The watch() function takes three arguments. The first argument is the name of the file to watch. The second argument is optional, and provides configuration settings. If present, the second argument should be an object containing a Boolean value named persistent. If true, persistent prevents the program from terminating. If the second argument is omitted, it defaults to true. The final argument is a callback which is triggered when the target file is modified. The callback passes in the type of event (change, rename, etc.) and the name of the file. It is worth noting that watch() is dependent on the underlying OS, and might not work on every system. If watch() is unavailable, the slower watchFile() can be used as a backup.

Conclusion

This article has introduced the Node.js file system module at a very high level. The module contains over 50 different functions, which is obviously too much for one article. For example, this article has only touched on reading files, and has completely neglected writing to files. I encourage you to browse the module documentation to get a more in depth understanding. And remember, the synchronous functions should be used with extreme caution!