Mastering Node.JS: How To Delete Files Inside A Nested Folder

Written by rockyessel | Published 2023/05/24
Tech Story Tags: nodejs | expressjs | javascript-development | nodejs-express-app | nested-objects | nodejs-programming-language | node.js-development | optimization

TLDRThe `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. To delete files inside a nested folder, you can follow these steps:via the TL;DR App

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:

  • fs.unlink(): Used to delete a file.
  • fs.readdir(): Used to read the contents of a directory.
  • fs.stat(): Used to retrieve the metadata of a file or directory.
  • stats.isFile(): Used to check if a path represents a file.
  • stats.isDirectory(): Used to check if a path represents a directory.
  • fs.rmdir(): Used to remove an empty directory.

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:

  • Importing the modules:

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

    We talked about the fs module, while the path module provides utilities for working with file and directory paths.

  • Defining the directory and file to delete:

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

  • The directory variable represents the path to the directory where the file is located. It uses the path.join() method to construct the full path based on the current directory (__dirname) and the relative path segments ('..', 'src', 'uploads', 'images').

    The file_to_delete variable contains the name of the file that you want to delete.

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:

  • The top-level directory is src, which typically contains the source code for a project.
  • Inside the src directory, there are several subdirectories:
    • controllers and models are likely directories where the application's controllers and models are stored, respectively. These directories might contain code files related to the application's business logic and data models.

    • The uploads directory is a directory used for storing uploaded files.

      • Inside the uploads directory, there are subdirectories:
        • images directory, which contains the file 1676410030129-screen.webp. This file is the one you want to delete.
        • audio directory, which may contain audio files.
        • videos directory, which may contain video files.
    • Finally, there is an index.js file, which is likely the entry point or main file of the application.

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:

  • The code begins with the fs.readdir() function, which reads the contents of the directory specified by the f variable.

  • f is the directory path that is passed as the first parameter to fs.readdir(). This variable should represent the path to the directory you want to read the contents of.

  • If an error occurs during the directory read operation, an error is thrown with the message 'Could not read directory'.

  • The files.forEach() method is used to iterate over each file and directory in the files array.

  • Inside the loop, the file_path variable is created by joining the f (directory path) and file (current file/directory name) using path.join(). This provides the full path to the current file or directory.

  • The file_path is logged to the console using console.log().

  • The fs.stat() function is called to retrieve the metadata of the current file or directory.

  • If an error occurs during the fs.stat() operation, an error is thrown with the message 'File does not exist'.

  • If the retrieved stat indicates that the current item is a directory, the message 'The file is actually a directory' is logged to the console.

  • If the current file name (file) matches the file_to_delete variable, the file is deleted using fs.unlink().

  • If an error occurs during the file deletion (fs.unlink()), an error is thrown with the message 'Could not delete file'.

  • Finally, a message is logged to the console indicating that the file has been deleted.

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.


Written by rockyessel | I'm a MERN stack developer, and currently learning Solidity and Rust programming and I like writing and music.
Published by HackerNoon on 2023/05/24