Categories
JavaScript Nodejs

Node.js FS Module — Opening Directories

Spread the love

Manipulating files and directories are basic operations for any program. Since Node.js is a server side platform and can interact with the computer that it’s running on directly, being able to manipulate files is a basic feature. Fortunately, Node.js has a fs module built into its library. It has many functions that can help with manipulating files and folders. File and directory operation that are supported include basic ones like manipulating and opening files in directories. Likewise, it can do the same for files. It can do this both synchronously and asynchronously. It has an asynchronous API that have functions that support promises. Also it can show statistics for a file. Almost all the file operations that we can think of can be done with the built in fs module. In this article, we will use the functions in the fs module to open and manipulate directories. We will also experiment with the fs promises API to do equivalent operations that exist in the regular fs module if they exist in the fs promises API. The fs promise API functions return promises so this let us run asynchronous operations sequentially much more easily.

Opendir Promise

The fs module can open, manipulate and close directories. Opening directories with the module is a simple task. To open a directory in a Node.js program, we can use the opendir function to do it. It takes a path object as an argument. The path can be a string, Buffer or an URL object. To open a directory, we can write the following code with the fs promise API, which is available in Node.js 12 LTS or later:

const fs = require("fs");

(async (path) => {  
  const dir = await fs.promises.opendir(path);  
  for await (const dirent of dir) {  
    console.log(dirent.name);  
  }  
})('./files');

The code above will list all the files and folders that are in the given folder. If you run the code above, you should get something like the following:

.keep  
file.txt  
file2.txt

Readdir

If you’re using a Node.js version that’s before 12, then we have to use the older fs API, which doesn’t have functions that return promises. We have to use the readdir function instead to list the contents of a directory. The readdir function takes 2 arguments. The first is the path object, which can be a string, an URL object or a Buffer, and the second argument is a callback function that’s called when the result is produced. The first parameter of the callback is the error object which is defined when there’s an error, and the second is an array with the actual results. For example, if we want to view the content of the ./files folder, we can write:

const fs = require("fs");

fs.readdir("./files", (err, items) => {  
  for (const dirent of items) {  
    console.log(dirent);  
  }  
});

The code above will get us similar output if we run it. It will open the directory path asynchronously, and then list the contents when it’s ready. If we have the same content as the directory above, we will see the following:

.keep  
file.txt  
file2.txt

There’s also a synchronous version of the readdir function called the readdirSync . It takes one argument, which is the path object, which can be a string, an URL object or a Buffer. It returns an array with the content of the given directory path. We can do the same thing by writing the following code:

const fs = require("fs");

const items = fs.readdirSync("./files");  
for (const dirent of items) {  
  console.log(dirent);  
}

If we run the code above, we get the same output as we did before if we have the same contents in the given directory as before.

The opendir function and its synchronous version opendirSync function let us get the directory resource handle of the folder with the given path object. The path object can be a string, an URL object or a buffer. Then asynchronous version, opendir , takes the path object as the first argument and callback function which takes the error object as the first parameter and the directory resource handle object as the second parameter. It’s called when the directory is opened. With the directory handle

Whenever you’re done with opening the directory with the opendir, opendirSync, and manipulating it, we should close it to clean up any reference of it from memory to prevent memory leaks. To do this, Node.js has a close function, which takes a callback function with an error object parameter to get the error when there’s one. It will close the open directory’s resource handle asynchronously. There’s also a synchronous version called the closeSync function that takes no arguments and closes the directory’s resource handle like the close function did, but it holds up the Node.js program’s process until it’s finished. This doesn’t apply to the fs promise API version of opendir since it closes it automatically. For example, if we want to open a directory asynchronously, we can write:

fs.opendir("./files", (err, dir) => {  
  console.log(dir);  
  dir.read((err, dirent) => {  
    console.log(dirent);  
    dir.close();  
  });  
});

The code above will open the directory with the opendir function, which takes a path object and a callback that takes an error object and the directory handle as parameters. Then we call the read function to open the directory and get its description and the name..

Then we call close on the directory resource handle object in the callback passed into the read function which closes the resource and frees up the memory. If we run it, we will get output the looks something like the following:

Dir {  
  [Symbol(kDirHandle)]: DirHandle {},  
  [Symbol(kDirPath)]: './files',  
  [Symbol(kDirClosed)]: false,  
  [Symbol(kDirOptions)]: { encoding: 'utf8' },  
  [Symbol(kDirReadPromisified)]: [Function: bound read],  
  [Symbol(kDirClosePromisified)]: [Function: bound close]  
}  
Dirent { name: '\u0010S.u\u0001', [Symbol(type)]: 777167552 }

OpendirSync

A synchronous version of the opendir function, which is the opendirSync function, can be used with the readSync function to get the directory’s metadata. For example, we can write:

const fs = require("fs");const dir = fs.opendirSync("./files");  
const dirData = dir.readSync();  
console.log(dirData)  
dir.closeSync();

The dirData variable will have something like the following output:

Dirent { name: '.keep', \[Symbol(type)\]: 1 }

That is the data of the content of the directory. To get the path of the opened directory, we can use the path function, which is a function that’s part of the directory object. For example, we can write the following to get the path of the currently opened directory:

const fs = require("fs");fs.opendir("./files", (err, dir) => {  
  console.log(dir.path);  
});

If we run the code above, the we will get output that’s similar to the following:

./files

The output row is the output of the first console.log statement, which is the path of the currently open directory. This isn’t very useful here, but it can be used when the directory resource handle is from an indefinite origin, like when the directory resource handle object is passed in as an argument of a function.

To read the contents of the directory with a directory resource handle, we can use the read function, which is part of the directory resource handle object.

It takes no arguments and a promise is returned that’ll be resolved with the fs.Dirent object, which is an iterable object that has the names of the items in a directory.

The read function without any arguments returns a promise that resolves with the same Dirent object as the example above. For example, we can use the read like in the following code with the asynchronous opendir function:

const fs = require("fs");fs.opendir("./files", (err, dir) => {  
  dir.read((err, dirent) => {  
    console.log(dirent);  
    dir.close();  
  });  
});

If we run the code, we get something like the following output:

Dirent { name: '.keep', \[Symbol(type)\]: 1 }

The output above has the Dirent object, which has the name of the first file in the directory.

<img class="s t u gw ai" src="https://miro.medium.com/max/5854/0*M3wUa7pqdaGu7xMY" width="2927" height="3903" srcSet="https://miro.medium.com/max/552/0*M3wUa7pqdaGu7xMY 276w, https://miro.medium.com/max/1104/0*M3wUa7pqdaGu7xMY 552w, https://miro.medium.com/max/1280/0*M3wUa7pqdaGu7xMY 640w, https://miro.medium.com/max/1400/0*M3wUa7pqdaGu7xMY 700w" sizes="700px" role="presentation"/>

Photo by Omid Kashmari on Unsplash

Listing Multiple Pieces of Directory Content with Opendir

We can read more than one file in the directory, by nested the read function in the callback of the outer read function. For example, we can write:

const fs = require("fs");fs.opendir("./files", (err, dir) => {  
  console.log(dir.path);  
  dir.read((err, dirent) => {  
    console.log(dirent);  
    dir.read((err, dirent) => {  
      console.log(dirent);  
      dir.close();  
    });  
  });  
});

Then we get output with something like the following text:

./files  
Dirent { name: '.keep', \[Symbol(type)\]: 1 }  
Dirent { name: 'file.txt', \[Symbol(type)\]: 1 }

The first row of the output contains the folder path, which we got with dir.path . The next 2 rows are the first 2 items of the directory. Every time we call dir.read , we get the next item of the directory.

To read the contents of the directory with a directory resource handle, we can use the read function, which is part of the directory resource handle object. It takes no arguments and a promise is returned that’ll be resolved with the fs.Dirent object, which is an iterable object that has the names of the items in a directory. The read function without any arguments returns a promise that resolves with the same dirent object as the example above. For example, we can use the read like in the following code with the asynchronous opendir function:

const fs = require("fs");fs.opendir("./files", async (err, dir) => {  
  console.log(dir.path);  
  const item = await dir.read();  
  console.log(item)  
  dir.close();  
});

If we run the code above, we get something like the following:

./files  
Dirent { name: '.keep', \[Symbol(type)\]: 1 }

If we want to get the first 2 items in the directory, with the promise version of dir.read , we can write the following:

const fs = require("fs");fs.opendir("./files", async (err, dir) => {  
  console.log(dir.path);  
  const item1 = await dir.read();  
  console.log(item1)  
  const item2 = await dir.read();  
  console.log(item2)  
  dir.close();  
});

We should get the same output as what we had above assuming we got the same folder contents as before. As we can see, it’s not practical to use those methods to get more items inside the directory those 2 examples as have above of the read function. If we want to get more than 2 items in the directory with the read function, we can write the following with the promise version of the read function, which takes no arguments:

const fs = require("fs");fs.opendir("./files", async (err, dir) => {  
  while ((item = await dir.read())) {  
    console.log(item);  
  }  
  dir.close();  
});

If we run the code above, we get something like the following output:

Dirent { name: '.keep', \[Symbol(type)\]: 1 }  
Dirent { name: 'file.txt', \[Symbol(type)\]: 1 }  
Dirent { name: 'file2.txt', \[Symbol(type)\]: 1 }  
Dirent { name: 'folder1', \[Symbol(type)\]: 2 }  
Dirent { name: 'folder2', \[Symbol(type)\]: 2 }

Note that we can use the await keyword inside a while loop to repeatedly run the dir.read() function call until null or undefined is returned. In the code above, we looped through all the items in the directory and logged the content of the Dirent object.

To check if a Dirent object is a directory, we can use the isDirectory() function. Likewise, we can check if an Dirent is a file with the isFile() function. For example, we can write the following to check if a Dirent is a file or a folder for the contents of a given directory with the following code:

const fs = require("fs");

fs.opendir("./files", async (err, dir) => {  
  while ((item = await dir.read())) {  
    console.log(item);  
    console.log(`Is directory: ${item.isDirectory()}`);  
    console.log(`Is file: ${item.isFile()}\n`);  
  }  
  dir.close();  
});

When we run the code above, we get something like the following output:

Dirent { name: '.keep', [Symbol(type)]: 1 }  
Is directory: false  
Is file: trueDirent { name: 'file.txt', [Symbol(type)]: 1 }  
Is directory: false  
Is file: trueDirent { name: 'file2.txt', [Symbol(type)]: 1 }  
Is directory: false  
Is file: trueDirent { name: 'folder1', [Symbol(type)]: 2 }  
Is directory: true  
Is file: falseDirent { name: 'folder2', [Symbol(type)]: 2 }  
Is directory: true  
Is file: false

As we can see, the isDirectory and isFile functions can identify if an item in a directory is another directory and it can also identify whether it’s a file or not.

With the Node.js’ built in fs module, we can open directories and list its contents easily. It has many functions that can help with opening directories and files inside it. It can do this both synchronously and asynchronously. It has an asynchronous API that have functions that support promises.

Also, it can show statistics for a file. Almost all the file operations that we can think of can be done with the built-in fs module. In this article, we used the functions in the fs module to open directories and list directory contents.

We also used some functions in the fs module that returns promises. The fs functions that return promises let us run asynchronous operations sequentially much more easily.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *