7 Methods For Working With Directories In Node.js
Node.js has a built-in Fs core module that exposes an API for interacting with the file system. Within that API, it has several methods specifically designed for working with directories.
In this article, we'll utilize the functionality provided by the fs
module to show you seven different methods for working with directories and provide you with lots of code samples along the way.
To get the code in this article to work, make sure you have Node.js installed on your local machine.
If you need one, we created a guide on installing Node.js.
Let's get started!
Table of Contents
- Check If A Directory Exists
- Create A New Directory
- List All The Files In A Directory (Including Sub-Directories)
- Get The Number Of Files In A Directory
- Get The Combined Size Of All Files In A Directory
- Rename A Directory
- Delete An Empty Or Non-Empty Directory
1. Check If A Directory Exists
The first method we'll go over is how to check if a directory exists. fs
has both a synchronous fs.access()
and asynchronous fs.existsSync()
function that can be used to check for the existence of a directory.
Method 1 - fs.access()
Let's go over the fs.access()
method first. This function takes a directory path as an argument and is usually used to test a user's permission for a file or directory.
But, if the given path isn't found, the function will return an error.
Therefore, we can detect if a directory exists by seeing if the fs.access()
function returns an error or not.
Here's an example of what the code would look like:
const fs = require("fs")
fs.access("./directory-name", function(error) {
if (error) {
console.log("Directory does not exist.")
} else {
console.log("Directory exists.")
}
})
The first thing we do is import the fs
module. Since this is built into Node.js, we don't need to install any NPM packages.
Then, we use the fs.access()
function and pass a directory path to it as an argument. And we also pass a callback function as an additional parameter that returns an error if one exists.
If an error exists, that means the file doesn't exist. So, we use an if...else
statement inside the fs.access()
function that logs a different message depending on whether the file exists or not.
Method 2 - fs.existsSync()
As the name suggests, the fs.existsSync()
function tests whether or not a given path exists in the filesystem.
The synchronous function takes a file sytem path as its sole parameter.
Here's what the code looks like:
const fs = require("fs")
try {
if (fs.existsSync("./directory-name")) {
console.log("Directory exists.")
} else {
console.log("Directory does not exist.")
}
} catch(e) {
console.log("An error occurred.")
}
Since fs.existsSync()
is synchronous, we create a try...catch
method to wrap the code in to catch any errors that may occur.
Inside the try...catch
statement, we use an if...else
statement where we use the fs.existsSync()
function to check if the given path to the directory exists.
When you run the code, a message will be logged indicating whether or not the directory exists.
In the next section, we'll show you how to create new directories.
2. Create A New Directory
Another function that the fs
module provides is fs.mkdir()
. This will make creating a new directory or folder super easy.
We'll go over both a basic example of creating a directory and how to create multiple levels of parent directories as well.
Basic Example
First, let's go over a basic example of how to use fs.mkdir()
in your code.
Here's the code:
const fs = require("fs")
fs.mkdir("./new-directory-name", function(err) {
if (err) {
console.log(err)
} else {
console.log("New directory successfully created.")
}
})
Let's break down what's going on in the code.
We require()
the fs
module and then pass the path of the new directory as an argument to the fs.mkdir()
function.
A callback function is also passed as an argument to function. If no error occurs, the directory is successfully created.
When you run the code, either a success or error message will be logged.
Create Parent Directories
What if you want to recursively create multiple levels of directories that don't exist yet?
The fs.mkdir()
function has an optional recursive
boolean value you can pass as a parameter.
You must have Node v10.12 or greater installed for this functionality to work.
Also, it's NOT supported on Windows platforms.
Here's the code sample:
const fs = require("fs")
fs.mkdir("./files/a/new-directory-name", { recursive: true }, function(err) {
if (err) {
console.log(err)
} else {
console.log("New directory successfully created.")
}
})
That code will create a new directory at a path of /files/a/new-directory-name
whether or not the /files
or files/a
directories exist already.
If they don't exist, fs.mkdir()
will create them along with the /new-directory-name
directory.
Other than that, the code works the same as the basic example from the previous section.
Remember, the recursive functionality is not supported on Windows and an error will be returned if it's used on that platform.
3. List All The Files In A Directory (Including Sub-Directories)
The next method we'll cover is how to create a list of all the files in a directory. We'll also show you how to list out files located in sub-directories as well.
We'll show you both a basic example of how to use the fs.readdirSync()
function and also how to use it in a more advanced way to recursively go through each subdirectory and get each of their files as well.
Basic Usage Of fs.readdirSync()
Before we create the recursive solution, let's go over a simple example using the fs.readdirSync()
function.
The fs.readdirSync()
function takes a directory path and a callback function for arguments. And it returns an array of the files as a relative path to the directory you passed as a parameter.
Here's what it looks like in practice, assuming you have a directory named directory-name
with files in it:
const fs = require("fs")
try {
const arrayOfFiles = fs.readdirSync("./directory-name")
console.log(arrayOfFiles)
} catch(e) {
console.log(e)
}
Let's go over what's happening in the code.
Since fs.readdirSync()
is a synchronous function, we wrap our code in a try...catch
statement to help manage our code and catch any errors that might occur.
Inside the try
section, we pass a path to a directory named directory-name
to the fs.readdirSync()
function and hold it in a variable called arrayOfFiles
.
When that variable is logged, you'll have an array of relative file paths to each file and subdirectory inside the directory.
The output will look similar to this:
["example.txt", "sub-directory"]
What if our directory has sub-directories that have files we want to include in our list?
In the next section, we'll show you how to get those as well.
Get All The Files In The Directory
Next, we'll go over how to recursively get all the files in a directory (even those located in a subdirectory).
To do this, we need to create a recursive function that can call itself when dealing with sub-directories. And we also need the function to go through each of the sub-directories and add any new files it encounters to the list. When the function is finished, it should return an array with all the files it encountered.
Here's what the recursive function looks like:
const fs = require("fs")
const path = require("path")
const getAllFiles = function(dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
arrayOfFiles.push(path.join(__dirname, dirPath, "/", file))
}
})
return arrayOfFiles
}
Let's go over each part of the code.
First, we require()
the Node.js path
module. Since this is included with Node.js, you don't need to install anything for it to work. This module will help us easily create full file paths for our files.
The getAllFiles
variable holds the recursive function that will go through each sub-directory and return an array of filenames. It takes a directory file path and an optional arrayOfFiles
as arguments.
Inside the getAllFiles
function, we first use the readdirSync()
function to get all of the files and directories inside the given dirPath
supplied to the function.
Then, we create an arrayOfFiles
that will hold all the filenames that will be returned when the function is done running.
Next, we loop over each item (file or directory) found by the readdirSync()
function. If the item is a directory, we have the function recursively call itself to get all of the files and sub-directories inside the given directory.
And if the item is a file, we simply append the file path to the arrayOfFiles
array.
When the forEach
loop has finished, we return the arrayOfFiles
array.
Here is how you use the function in your code:
const result = getAllFiles("PATH_TO_PARENT_DIRECTORY")
// [ "FILE_PATH", "FILE_PATH", "FILE_PATH" ]
There you have it! When you call the getAllFiles
function, it'll return a list of all the files.
Remember that this function is synchronous and therefore it will block the Node.js sync interface. And if you used the function on a deep directory structure such as root, you'll likely get a stack overflow exception.
To avoid that, you can replace the fs.readdirSync()
method in the function with it's fs.readdir()
synchronous counterpart.
4. Get The Number Of Files In A Directory
To get the number of files in a directory, we can use the recursive function from the last section to get an array of all the files located in a given directory. And we can then get the length of that array as a representation of how many files are in the directory.
Here's the recursive function again:
const fs = require("fs")
const path = require("path")
const getAllFiles = function(dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
arrayOfFiles.push(path.join(__dirname, dirPath, "/", file))
}
})
return arrayOfFiles
}
And you can simply get the length of that array if you wanted to know how many files were found:
const result = getAllDirFiles("PATH_TO_PARENT_DIRECTORY").length
// 3
When you log the value, an integer representing the number of files in the directory will be returned.
5. Get The Combined Size Of All Files In A Directory
Next, let's go over how we can get the combined size of all the files that reside in a directory. Here are the three coding steps you'll need to follow:
- Get all the files in the directory by recursively going through all sub-directories and returning an array of file paths.
- Loop through each file in the array of file paths, add-up their file size and return the value.
- Create a function that makes the bytes result from the previous step human readable.
Let's go through each step one at a time.
Step 1 - Get All The Files In The Directory
We can use our recursive function from earlier sections to get all the files in the directory.
Here's the full code once again:
const fs = require("fs")
const path = require("path")
const getAllFiles = function(dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
arrayOfFiles.push(path.join(__dirname, dirPath, "/", file))
}
})
return arrayOfFiles
}
This function can be used like this and will return an array of file path strings:
const result = getAllFiles("PATH_TO_PARENT_DIRECTORY")
// [ "FILE_PATH", "FILE_PATH", "FILE_PATH" ]
Now that we know how to get all the files in the directory, we can add the file size of each one together in the next step.
Step 2 - Add Up The Combined Byte Size Of Each File
Now let's create a function that takes a directory path, runs the getAllFiles
function we created early, and loops through each item in that array and adds together each file size value.
Add this function in the same file as you used in the last example:
const fs = require("fs")
const getTotalSize = function(directoryPath) {
const arrayOfFiles = getAllFiles(directoryPath)
let totalSize = 0
arrayOfFiles.forEach(function(filePath) {
totalSize += fs.statSync(filePath).size
})
return totalSize
}
We name our function getTotalSize
and configure it to take a directory path as its sole parameter.
Inside the function, the first thing we do is get the array of file paths using the getAllFiles
function we created in the last section.
Then, we use the forEach()
method to loop over each item in the array and increment the totalSize
value by the file size (in bytes).
When the loop is complete, the totalSize
value is returned.
You can call the function like this:
const result = getTotalSize("./directory-name")
// 163753
The integer returned is a bytes value as fs
only reads file sizes in those unit types. Therefore, the returned number doesn't mean much to humans.
In the next function, we'll build a quick function that formats that bytes value nicely and make it human-readable.
Step 3 - Convert Bytes To A Human-Readable Format
At this point, we have a bytes value that represents the total combined size of every file in the directory. But, what's the point of all that work we did in the previous sections if no one can understand the data we retrieved.
In this section, we'll build a function that takes a bytes value as a parameter and converts it into a formatted and human-readable KB, MB, GB, or TB version.
Here's the code:
const convertBytes = function(bytes) {
const sizes = ["Bytes", "KB", "MB", "GB", "TB"]
if (bytes == 0) {
return "n/a"
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
if (i == 0) {
return bytes + " " + sizes[i]
}
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i]
}
Before going through the code, it's worth noting that we use 1024
as the base unit for all conversions. We use that number because a kilobyte represents 1024
bytes in the binary system. And a megabyte is 1024
kilobytes and so on.
The first thing we do is create a function named convertBytes()
that takes a bytes
integer as its sole argument.
Inside the function, we create an array of strings named sizes
. This will store each of the potential labels (Bytes, Kilobyte, Megabyte, Gigabyte, Terabyte) that we'll use when creating a new string value representing our original bytes
parameter value. Each of these will be accessed by their index value later on in the function.
Then, we create an if statement that returns the string "n/a"
if the bytes
parameter value is equal to zero.
Next, we need to figure out what file type we need to use from the sizes
array. The variable named i
will represent the index of that string in the sizes
array. That value is determined by dividing the log of bytes
value by the log of 1024
(file sizes are based on those units).
If the i
index value is equal to 0
, we'll return a string with the "bytes"
label on it.
Otherwise, we'll return a string with the formatted bytes
value rounded to one decimal point. And the file type from the sizes
array will be added to the end of the string as well.
Here are some examples of the function in practice:
convertBytes(0) // "n/a"
convertBytes(500) // "500 Bytes"
convertBytes(2000) // "1.0 KB"
convertBytes(2024000) // "1.9 MB"
convertBytes(2550024000) // "2.4 GB"
convertBytes(1292550024000) // "1.2 TB"
As you can see, the function works as expected in converting a given bytes
integer into a human-readable string format.
Make sure you add the convertBytes
function to the same file as the other functions and you can use it when you return the final totalSize
value:
return convertBytes(totalSize)
When you run the function, you'll notice that the final value is no longer in bytes, but a nicely formatted and human-readable format instead.
For your reference, here's the full code:
const fs = require("fs")
const path = require("path")
const getAllFiles = function(dirPath, arrayOfFiles) {
files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles)
} else {
arrayOfFiles.push(path.join(__dirname, dirPath, file))
}
})
return arrayOfFiles
}
const convertBytes = function(bytes) {
const sizes = ["Bytes", "KB", "MB", "GB", "TB"]
if (bytes == 0) {
return "n/a"
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
if (i == 0) {
return bytes + " " + sizes[i]
}
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i]
}
const getTotalSize = function(directoryPath) {
const arrayOfFiles = getAllFiles(directoryPath)
let totalSize = 0
arrayOfFiles.forEach(function(filePath) {
totalSize += fs.statSync(filePath).size
})
return convertBytes(totalSize)
}
const result = getTotalSize("./directory-name")
// 159.9 KB
In the next section, we'll go over how to rename an existing directory to something new.
6. Rename A Directory
The fs
module provides an fs.rename()
and fs.renameSync()
function that handles the renaming of files and directories.
We'll show you how to use both of those functions. They both serve the same purpose, but one is synchronous and the other is asynchronous. Therefore, the code will look slightly different for each method.
Option 1 - fs.renameSync()
We'll go over the asynchronous fs.rename()
version first.
The fs.rename()
function takes two parameters: an old file path and a new file path. The old file path will be the current name of the directory we want to change and the new file path will be what we want to change it to.
Here's what the code looks like:
const fs = require("fs")
const currPath = "./directory-name"
const newPath = "./new-directory-name"
fs.rename(currPath, newPath, function(err) {
if (err) {
console.log(err)
} else {
console.log("Successfully renamed the directory.")
}
})
The first thing we do is create the currPath
and newPath
variables. These represent the path to the directory we want to change and the new name we want to give the directory.
Then, we use the fs.rename()
function and give it three parameters: the currPath
variable, the newPath
variable, and a callback function that returns an error if one occurs.
When you run the code, a success message will be logged. If an error occurred, the error message will be logged.
In the next section, we'll show you how to use the synchronous fs.renameSync()
version of the method we just went through.
Option 2 - fs.rename()
The fs.renameSync()
function is the synchronous version of fs.rename()
.
Both versions serve the same purpose and the fs.renameSync()
function takes an old path and new path as parameters. But, this method will stop your code while it runs, so we need to structure things slightly different than the previous example.
Here's what the code looks like:
const fs = require("fs")
const currPath = "./directory-name"
const newPath = "./new-directory-name"
try {
fs.renameSync(currPath, newPath)
console.log("Successfully renamed the directory.")
} catch(err) {
console.log(err)
}
Like the previous example, we create two variables that hold our old and new directory paths.
But, we use a try...catch
statement to handle our synchronous code. The catch
section will stop the function and log an error if one occurs. And inside the try
section, we call the fs.renameSync()
function and log a success message when it finishes.
7. Delete An Empty Or Non-Empty Directory
The last method we'll cover is how to delete both an empty and non-empty directory.
Deleting an empty directory is an easy process of simply using the fs.rmdir()
function. But the non-empty portion will be more tricky because we'll need to build a function that recursively goes through all of the sub-directories and removes each file it encounters.
We'll show you to use both methods.
Delete An Empty Directory
Let's quickly cover how to remove an empty directory using the fs.rmdir()
or fs.rmdirSync()
function.
Here is the code for how to use the asynchronous fs.rmdir()
method:
const fs = require("fs")
fs.rmdir("./directory-name", function(err) {
if (err) {
console.log(err)
} else {
console.log("Directory was deleted.")
}
})
The fs.rmdir()
function takes a path to the directory you want to remove and a callback function that will return an error if it encounters one.
Inside the callback function, we do some basic error handling and log a success message via console.log()
when the directory has been successfully removed.
And here is how you use the synchronous fs.rmdirSync()
version:
const fs = require("fs")
try {
fs.rmdirSync("./directory-name")
console.log("Directory was deleted.")
} catch(e) {
console.log(e)
}
The synchronous fs.rmdirSync()
version works the same as fs.rmdir()
, but it will block the Node.js event loop while it runs.
We wrap the code in the try...catch
statement that logs any errors if they occur and logs a success message when the directory has been successfuly removed.
Easy enough, right? In the next section, we'll delete a non-empty directory.
Delete A Non-Empty Directory Along With Its Contents
To delete a directory that contains sub-directories, we need to create a function that recursively loops through each and deletes every file it encounters.
Below we have written a function that recursively moves through a directory and all it's sub-directories and removes every file it finds.
We'll give you the full code and explain everything afterward:
const fs = require("fs")
const path = require("path")
const removeDir = function(path) {
if (fs.existsSync(path)) {
const files = fs.readdirSync(path)
if (files.length > 0) {
files.forEach(function(filename) {
if (fs.statSync(path + "/" + filename).isDirectory()) {
removeDir(path + "/" + filename)
} else {
fs.unlinkSync(path + "/" + filename)
}
})
fs.rmdirSync(path)
} else {
fs.rmdirSync(path)
}
} else {
console.log("Directory path not found.")
}
}
const pathToDir = path.join(__dirname, "directory-name")
removeDir(pathToDir)
A lot is going on here, so let's break down each section.
The removeDir
function is what we use to recursively remove a parent directory and it's files and sub-directories. The only parameter is the path to the parent directory.
Inside the removeDir
function, the first thing we do is use the fs.existsSync()
to verify that the directory we passed to our function exists. If it doesn't exist, we end the function and log an error message.
Then, we pass our directory path to the fs.readdirSync()
function to get an array of all the files and sub-directories inside the parent directory. And we store that array in a variable named files
.
If the array is empty, that means the directory is empty and we can use the fs.rmdirSync()
function to delete it.
If the array.length > 0
, we loop through the files
array with the forEach()
function. For each item in the files
array, we check whether or not the item is a file or a sub-directory.
If the item is a sub-directory, we have the function recursively call itself with removeDir()
, with the path of the sub-directory as a parameter. That function will loop through the sub-directory and remove it's children files and directories.
And if the item is a file, we simply use the fs.unlinkSync()
to remove the file, with the path to the file given as a parameter.
When the .forEach()
loop has finished and all the files have been removed, you can delete the now empty directory with the fs.rmdirSync()
function.
Then we call our removeDir(pathToDir)
function with the path to the parent directory as a parameter.
When you run the code, you should see that the directory in question is removed.
If you don't want to deal with writing your own function for this, there are is the Fs-Extra NPM package that makes this much easier to do.
That package provides functionality built specifically for this problem.
After reading this article, you now have seven different methods in your back pocket for when you need to deal with directories when coding in Node.js.
Thanks for reading and happy coding!