In Node.js, you can delete files inside a nested folder by utilizing the fs (file system) module. The fs module provides various methods for interacting with the file system, including deleting files.

Disclaimer: The fs module code examples were generated by AI, and work as intended for the goal of this post.

But before we continue, let's talk about the functions provided by the fs, that we can use to accomplish our goal in this post.

In order to delete a file or files in a nested directory we will need to use the following functions and methods provided by the fs module:

By utilizing these functions and methods together, you can recursively navigate through the nested folder structure, identify files and directories, and delete the desired files accordingly.

For new developers that want to know more about these methods, let’s talk about them with a few examples before moving to our main goal of this post.

The fs module in Node.js provides an API for interacting with the file system. It offers a wide range of functions to perform file-related operations, such as reading, writing, deleting, and modifying files and directories.

  1. fs.unlink(path, callback)

    • The fs.unlink() function is used to asynchronously delete a file specified by the path parameter.

    • It requires a callback function that will be called after the file is deleted or if an error occurs during the deletion.

      Example:

      fs.unlink('/path/to/file.txt', (err) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log('File deleted successfully');
      });
      
      

  2. fs.readdir(path, callback)

    • The fs.readdir() function is used to asynchronously read the contents of a directory specified by the path parameter.

    • It returns an array of filenames present in the directory.

      Example:

      fs.readdir('/path/to/directory', (err, files) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log('Files in directory:', files); // Shows all the files in the directory.
      });
      
      

  3. fs.stat(path, callback)

    • The fs.stat() function is used to asynchronously retrieve the metadata of a file or directory specified by the path parameter.

    • It provides information about the file, such as its size, permissions, and timestamps.

      Example:

      fs.stat('/path/to/file.txt', (err, stats) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log('File size:', stats.size);
        console.log('Last modified:', stats.mtime);
      });
      
      

  4. stats.isFile()

    • The stats.isFile() method is used to check if a file exists at the specified path. It returns true if the path represents a file; otherwise, it returns false.

      Example:

      fs.stat('/path/to/file.txt', (err, stats) => {
        if (err) {
          console.error(err);
          return;
        }
        if (stats.isFile()) {
          console.log('The path is a file'); //Returns true, if a file exist in the path defined
        } else {
          console.log('The path is not a file');//Returns false, if there's no in the defined path.
        }
      });
      
      

  5. stats.isDirectory()

    • The stats.isDirectory() method is used to check if a directory exists at the specified path. It returns true if the path represents a directory; otherwise, it returns false.

    • Example:

      fs.stat('/path/to/directory', (err, stats) => {
        if (err) {
          console.error(err);
          return;
        }
        if (stats.isDirectory()) {
          console.log('The path is a directory'); // Return true, if the path defined is a directory, and nothing else.
        } else {
          console.log('The path is not a directory'); // Returns false if the path defined is not directory. menaing the path defined doesn't exist.
        }
      });
      
      

  6. fs.rmdir(path, callback)

    • The fs.rmdir() function is used to asynchronously remove an empty directory specified by the path parameter.

    • It requires a callback function that will be called after the directory is removed or if an error occurs during the removal.

      Example:

      fs.rmdir('/path/to/empty-directory', (err) => {
        if (err) {
          console.error(err);
          return;
        }
        console.log('Directory removed successfully');
      });
      
      

That’s all, now you have a solid or some understanding of fs in general. So let’s move on to the main goal, which is to delete nested files.

import fs from 'fs'
import path from 'path'

const directory = path.join(__dirname, '..', 'src', 'uploads', 'images')
const file_to_delete = '1676410030129-screen.webp'

A breakdown of the code above:

Inside the src folder, the  uploads, and images are in a subfolder structure.

src
 ├── controllers
 ├── models
 ├── uploads
 │   ├── images
 │   │   └── 1676410030129-screen.webp
 │   ├── audio
 │   └── videos
 └── index.js

Here is a breakdown of our folder structure:

Overall, this folder structure suggests that the application follows a modular organization, separating controllers, models, and uploaded files into their respective directories under the src folder.

This is the whole code for deleting a file in a nested directory, but we are going to take it step by step, in the beginner approach.

fs.readdir(f, (error, files) => {
if (error) throw new Error('Could not read directory');

files.forEach((file) => {
const file_path = path.join(f, file);
console.log(file_path);

fs.stat(file_path, (error, stat) => {
  if (error) throw new Error('File do not exist');

  if(stat.isDirectory()){
    console.log('The file is actually a directory')
  }else if (file === file_to_delete ) {
    fs.unlink(file_path, (error)=> {
      if (error) throw new Error('Could not delete file');
        console.log(`Deleted ${file_path}`);
    })
  }

    });
   });
  });

Breakdown:

This code snippet essentially reads the directory contents, iterates over each file, checks if it is a directory or the file to be deleted, and performs the necessary actions accordingly.

Simple Explanation:

We first read the directory of the files we want then if we encounter an error, we throw an error indicating that something went wrong, but in this case, the error message will be Could not read directory.

If there’s no error, we get the files, which is an array of file names if multiple files are found or a single file if one file is found, we then grab each file in the array and then join the directory to the file, so we get something like this C:\Users\..\Desktop\..\..\src\uploads\images\1676410030129-screen.webp.

Then we use fs.stat to check if the path to the file exists, if there’s no file, we throw an error saying File do not exist.

We also check if the path to the actual file is a directory stat.isDirectory() if true, we console.log The file is actually a directory, if it is false, then we move to the following line of the code and check if the file name C:\User\..\1676410030129-screen.webp is strictly equal to the file_to_delete, if it is equal, then we delete the file or else throw an error saying Could not delete file.

So at this point, we are done with our logic, and you have also gained an understanding of the concept we’re implementing. So here is the full code to this point.

import fs from 'fs'
import path from 'path'

const directory = path.join(__dirname, '..', 'src', 'uploads', 'images')
const file_to_delete = '1676410030129-screen.webp'

fs.readdir(f, (error, files) => {
if (error) throw new Error('Could not read directory');

files.forEach((file) => {
const file_path = path.join(f, file);
console.log(file_path);

fs.stat(file_path, (error, stat) => {
  if (error) throw new Error('File do not exist');

  if(stat.isDirectory()){
    console.log('The file is actually a directory')
  }else if (file === file_to_delete ) {
    fs.unlink(file_path, (error)=> {
      if (error) throw new Error('Could not delete file');
        console.log(`Deleted ${file_path}`);
    })
  }

    });
   });
  });

Done? No, what if we want to use this code not only in one place, but three different files at the same time, meaning I will have to write the whole code in all three different files, which can use very stressful, and here is where the function comes. We won’t cover the scope for functions because this is something you should know at this point.

So the code below has been refactored and made reusable. Won’t explain further, since the code below has been already explained.

const directory = path.join(__dirname, '..', 'src', 'uploads', 'images');

export const handleFileDeletion = (directory: string, file_to_delete: string) => {
  fs.readdir(directory, (error, files) => {
    if (error) {
      console.log(error);
      throw new Error('Could not read directory');
    }

    files.forEach((file) => {
      const file_path = path.join(directory, file);

      fs.stat(file_path, (error, stat) => {
        if (error) {
          console.log(error);
          throw new Error('Could not stat file');
        }

        if (stat.isDirectory()) {
          // Here instead of doing a consle.log(),
          // we recursively search for the file in subdirectories
          handleFileDeletion(file_path, file_to_delete);
        } else if (file === file_to_delete) {
          fs.unlink(file_path, (error) => {
            if (error) {
              console.log(error);
              throw new Error('Could not delete file');
            }

            console.log(`Deleted ${file_path}`);
          });
        }
      });
    });
  });
};

// Calling the function
handleFileDeletion(directory, '1676410030129-screen.webp')

Done? Yes, we are.

Conclusion

Overall, we learned about the necessary functions and techniques involved in deleting files within a nested folder structure using the fs module in Node.js.


Also published here.

I posted an answer on Stack Overflow and you can find the use case on code on GitHub here.