Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Directory Inspection Tool | Building Console Applications with Node.js
Backend Development with Node.js and Express.js

Directory Inspection Tool

Swipe to show menu

This chapter presents you with a challenge: creating an advanced console app named DirInspect Pro. This app will empower you to thoroughly analyze any directory and gain insightful statistics about its files and subdirectories.

Challenge

Picture a scenario where you must navigate a labyrinth of folders containing crucial files and data. DirInspect Pro is your ally in this journey, providing comprehensive insights into the directory's structure and contents.

The Resulting App

Get ready to wield DirInspect Pro's capabilities. The app will furnish you with critical information, such as

  • The total number of items;
  • The aggregate size of all items;
  • The largest file's name and size;
  • The detailed list of individual file names and sizes.

Two Paths to Choose

You have two paths ahead.

  • The first is to tackle this challenge head-on, honing your skills without guidance;
  • The second is to follow a helpful guide that guarantees your success.

Whichever path you choose, you're in for a rewarding journey culminating in creating a captivating and functional console app.

Masterplan

  • Step 1: Import Required Modules;
  • Step 2: Define getStats Function;
  • Step 3: Define analyzeFile Function;
  • Step 4: Define analyzeDirectory Function;
  • Step 5: Define main Function and Invoke;
  • Conclusion;
  • Full App Code.

Step 1: Import Required Modules

To embark on this adventure, you'll need the right tools. Begin by importing two key modules: fs.promises to manage the file system asynchronously and path to handle file paths effectively.

const fs = require("fs").promises;
const path = require("path");
Code description
expand arrow
  • Line 1: imports the built-in fs module. The .promises property provides access to the promises-based API, allowing asynchronous file operations to be written using async/await;
  • Line 2: imports the built-in path module. This module provides utilities for working with file and directory paths, including path manipulation and normalization.

Step 2: Define getStats Function

The asynchronous function, getStats, takes a file or directory path as an argument and attempts to retrieve its statistics using fs.stat.

  • If successful, it returns the statistics;
  • If an error occurs, it logs an error message and returns null.
async function getStats(filePath) {
  try {
    const stats = await fs.stat(filePath);
    return stats;
  } catch (err) {
    console.error("Error getting stats:", err.message);
    return null;
  }
}
Code description
expand arrow
  • Line 1: declares an asynchronous function named getStats that accepts a filePath parameter;
  • Lines 2, 5: create a try...catch block used for error handling during the asynchronous operation;
  • Line 3: uses await fs.stat(filePath) to retrieve information about the specified file, such as its size, creation time, and other metadata;
  • Line 4: returns the stats object if the fs.stat() operation completes successfully;
  • Line 6: logs an error message to the console using console.error() if the operation fails;
  • Line 7: returns null to indicate that the file statistics could not be retrieved.

Step 3: Define analyzeFile Function

The analyzeFile function uses the getStats function to obtain statistics for a file. If statistics are available (not null), it returns an object containing the file's name (extracted using path.basename) and its size.

async function analyzeFile(filePath) {
  const stats = await getStats(filePath);
  if (!stats) return null;

  return {
    name: path.basename(filePath),
    size: stats.size,
  };
}
Code description
expand arrow
  • Line 1: declares an asynchronous function named analyzeFile that accepts a filePath parameter;
  • Line 2: uses await getStats(filePath) to retrieve file statistics for the provided path;
  • Line 3: checks whether stats is falsy. If the statistics could not be retrieved, the function returns null;
  • Lines 5-8: create and return an object containing the file name and file size. The file name is extracted using path.basename(filePath), while the size is retrieved from stats.size.

Step 4: Define analyzeDirectory Function

The analyzeDirectory function scans a directory and gathers full statistics about its contents. How it works: it reads all items inside the directory using fs.readdir. For each item:

  • Builds the full path with path.join;
  • Uses getStats to detect whether it's a file or a folder;
  • If it's a file:
    • Calls analyzeFile to get { name, size };
    • Updates totals, largest file, and the file list.
  • If it's a directory:
    • Recursively calls analyzeDirectory;
    • Merges results into the current statistics.
async function analyzeDirectory(directoryPath) {
  let totalItems = 0;
  let totalFiles = 0;
  let totalSize = 0;
  let largestFile = { name: "", size: 0 };
  let fileList = [];

  try {
    const items = await fs.readdir(directoryPath);

    for (const item of items) {
      const itemPath = path.join(directoryPath, item);
      const stats = await getStats(itemPath);
      if (!stats) continue;

      totalItems++;

      if (stats.isFile()) {
        const fileInfo = await analyzeFile(itemPath);
        if (!fileInfo) continue;

        totalFiles++;
        totalSize += fileInfo.size;

        if (fileInfo.size > largestFile.size) {
          largestFile = fileInfo;
        }

        fileList.push(fileInfo);
      } else if (stats.isDirectory()) {
        const subDirectoryStats = await analyzeDirectory(itemPath);

        totalItems += subDirectoryStats.totalItems;
        totalFiles += subDirectoryStats.totalFiles;
        totalSize += subDirectoryStats.totalSize;

        if (subDirectoryStats.largestFile.size > largestFile.size) {
          largestFile = subDirectoryStats.largestFile;
        }

        fileList = fileList.concat(subDirectoryStats.fileList);
      }
    }

    return {
      totalItems,
      totalFiles,
      totalSize,
      largestFile,
      fileList
    };
  } catch (err) {
    console.error("Error analyzing directory contents:", err.message);
    return {
      totalItems: 0,
      totalFiles: 0,
      totalSize: 0,
      largestFile: { name: "", size: 0 },
      fileList: []
    };
  }
}
Code description
expand arrow
  • Line 1: declares an asynchronous function named analyzeDirectory that accepts a directoryPath parameter;

  • Lines 2-6: initialize variables used to store directory statistics, including total items, total files, total size, the largest file, and a list of analyzed files;

  • Line 8: starts a try block to handle potential filesystem errors;

  • Line 9: reads the contents of the directory using fs.readdir() and stores the result in the items array;

  • Line 11: starts a for...of loop to iterate through each item in the directory;

  • Line 12: creates the full path for the current item using path.join();

  • Line 13: retrieves statistics for the current item using getStats();

  • Line 14: skips the current item if its statistics could not be retrieved;

  • Line 16: increments totalItems for every successfully processed file or directory;

  • Lines 18-29: handle file processing:

    • Line 19: calls analyzeFile() to retrieve the file name and size;
    • Line 20: skips the file if the analysis fails;
    • Lines 22-23: update the total file count and accumulated file size;
    • Lines 25-27: update largestFile if the current file is larger than the previously recorded one;
    • Line 29: adds the file information to fileList;
  • Lines 30-42: handle directory processing:

    • Line 31: recursively calls analyzeDirectory() for the subdirectory;
    • Lines 33-35: merge the subdirectory statistics into the current totals;
    • Lines 37-39: update largestFile if the subdirectory contains a larger file;
    • Line 41: merges file lists using concat();
  • Lines 45-50: return an object containing the final directory statistics;

  • Lines 52-62: handle errors by logging a message and returning a default statistics object.

Step 5: Define main Function and Invoke

The main function is the entry point of the script. It specifies the directory path to analyze (in this case, ./docs), calls the analyzeDirectory function to obtain the statistics of the directory and its contents, and then outputs the collected information. The function prints out

  • The total number of items;
  • The total number of files;
  • The total size;
  • The details about the largest file;
  • The list of files in the directory.
async function main() {
  const directoryPath = "./docs";
  const directoryStats = await analyzeDirectory(directoryPath);

  console.log("Directory Analysis:");
  console.log("Total items:", directoryStats.totalItems);
  console.log("Total files:", directoryStats.totalFiles);
  console.log("Total size (bytes):", directoryStats.totalSize);
  console.log(
    "Largest file:",
    directoryStats.largestFile.name,
    "Size:",
    directoryStats.largestFile.size,
    "bytes"
  );

  console.log("\nFile List:");
  for (const file of directoryStats.fileList) {
    console.log(file.name, "Size:", file.size, "bytes");
  }
}

main();
Code description
expand arrow
  • Line 1: declares an asynchronous function named main;

  • Line 2: defines the directoryPath variable, which stores the path to the directory that will be analyzed;

  • Line 3: calls analyzeDirectory() with the specified directoryPath and stores the result in the directoryStats variable using await;

  • Lines 5-17: display the calculated directory statistics using console.log(), including the total number of items, total files, total size, and information about the largest file;

  • Lines 18-20: iterate through the fileList array stored in directoryStats;

    • log the name and size of each file in the directory;
  • Line 23: calls the main() function to start the analysis process.

Full App Code

const fs = require("fs").promises;
const path = require("path");

async function getStats(filePath) {
  try {
    const stats = await fs.stat(filePath);
    return stats;
  } catch (err) {
    console.error("Error getting stats:", err.message);
    return null;
  }
}

async function analyzeFile(filePath) {
  const stats = await getStats(filePath);
  if (!stats) return null;

  return {
    name: path.basename(filePath),
    size: stats.size
  };
}

async function analyzeDirectory(directoryPath) {
  let totalItems = 0;
  let totalFiles = 0;
  let totalSize = 0;
  let largestFile = { name: "", size: 0 };
  let fileList = [];

  try {
    const items = await fs.readdir(directoryPath);

    for (const item of items) {
      const itemPath = path.join(directoryPath, item);
      const stats = await getStats(itemPath);
      if (!stats) continue;

      totalItems++;

      if (stats.isFile()) {
        const fileInfo = await analyzeFile(itemPath);
        if (!fileInfo) continue;

        totalFiles++;
        totalSize += fileInfo.size;

        if (fileInfo.size > largestFile.size) {
          largestFile = fileInfo;
        }

        fileList.push(fileInfo);
      } else if (stats.isDirectory()) {
        const subDirectoryStats = await analyzeDirectory(itemPath);

        totalItems += subDirectoryStats.totalItems;
        totalFiles += subDirectoryStats.totalFiles;
        totalSize += subDirectoryStats.totalSize;

        if (subDirectoryStats.largestFile.size > largestFile.size) {
          largestFile = subDirectoryStats.largestFile;
        }

        fileList = fileList.concat(subDirectoryStats.fileList);
      }
    }

    return {
      totalItems,
      totalFiles,
      totalSize,
      largestFile,
      fileList
    };
  } catch (err) {
    console.error("Error analyzing directory contents:", err.message);
    return {
      totalItems: 0,
      totalFiles: 0,
      totalSize: 0,
      largestFile: { name: "", size: 0 },
      fileList: []
    };
  }
}

async function main() {
  const directoryPath = "./docs";
  const directoryStats = await analyzeDirectory(directoryPath);

  console.log("Directory Analysis:");
  console.log("Total items:", directoryStats.totalItems);
  console.log("Total files:", directoryStats.totalFiles);
  console.log("Total size (bytes):", directoryStats.totalSize);

  console.log(
    "Largest file:",
    directoryStats.largestFile.name,
    "Size:",
    directoryStats.largestFile.size,
    "bytes"
  );

  console.log("\nFile List:");
  for (const file of directoryStats.fileList) {
    console.log(file.name, "Size:", file.size, "bytes");
  }
}

main();
Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 2. Chapter 10

Ask AI

expand

Ask AI

ChatGPT

Ask anything or try one of the suggested questions to begin our chat

Section 2. Chapter 10
some-alt