Exercise - Work with the file system

Completed

Tailwind Traders has many physical stores all over the world. Each night, these stores create a file called sales.json that contains the total for all their sales for the previous day. These files are organized in folders named by store ID.

In this exercise, you write a Node.js program that can search for files called sales.json in a folder.

Open project in development container

  1. Start the process to create a new GitHub Codespace on the main branch of the MicrosoftDocs/node-essentials GitHub repository.

  2. On the Create codespace page, review the codespace configuration settings and then select Create new codespace

    Screenshot of the confirmation screen before creating a new codespace.

  3. Wait for the codespace to start. This startup process can take a few minutes.

  4. Open a new terminal in the codespace.

    Tip

    You can use the main menu to navigate to the Terminal menu option and then select the New Terminal option.

    Screenshot of the codespaces menu option to open a new terminal.

  5. Validate that Node.js is installed in your environment:

    node --version
    

    The dev container uses a Node.js LTS version such as v20.5.1. The exact version might be different.

  6. The remaining exercises in this project take place in the context of this development container.

Find the sales.json files

Your task is to find all the files in the stores folder.

Expand the stores folder and each of the numbered folders inside.

Screenshot that shows the project folder structure.

Include the fs module

  1. In the ./nodejs-files subfolder, create a index.js file to open it in the editor.

  2. At the top of the file, add the following code to include the fs module in the file.

    const fs = require("fs").promises;
    
  3. Next, create the main function which is the entry point for your code. The last line of code in this file invokes the main method.

    const fs = require("fs").promises;
    
    async function main() {}
    
    main();
    

    This is typical CommonJS boilerplate code to call an asynchronous function.

Write a function to find the sales.json files

  1. Create a new function called findSalesFiles that takes a folderName parameter.

    async function findSalesFiles(folderName) {
      // FIND SALES FILES
    }
    
  2. Inside the findSalesFiles function, add the following code to complete these tasks:

    • (1) Add an array at the top, to hold the paths to all the sales files that the program finds.
    • (2) Read the currentFolder with the readdir method.
    • (3) Add a block to loop over each item returned from the readdir method using the asynchronous for...of loop.
    • (4) Add an if statement to determine if the item is a file or a directory.
    • (5) If the item is a directory, recursively call the function findSalesFiles again, passing in the path to the item.
    • (6) If it's not a directory, add a check to make sure the item name matches sales.json.
    async function findSalesFiles(folderName) {
    
       // (1) Add an array at the top, to hold the paths to all the sales files that the program finds.
       let results = [];
    
       // (2) Read the currentFolder with the `readdir` method. 
       const items = await fs.readdir(folderName, { withFileTypes: true });
    
       // (3) Add a block to loop over each item returned from the `readdir` method using the asynchronous `for...of` loop. 
       for (const item of items) {
    
         // (4) Add an `if` statement to determine if the item is a file or a directory. 
         if (item.isDirectory()) {
    
           // (5) If the item is a directory, recursively call the function `findSalesFiles` again, passing in the path to the item. 
           const resultsReturned = await findSalesFiles(`${folderName}/${item.name}`);
           results = results.concat(resultsReturned);
         } else {
           // (6) If it's not a directory, add a check to make sure the item name matches *sales.json*.
           if (item.name === "sales.json") {
             results.push(`${folderName}/${item.name}`);
           }
         }
       }
    
       return results;
    }
    
  3. Call this new findSaleFiles function from the main method. Pass in the stores folder name as the location to search for files.

     async function main() {
       const results = await findSalesFiles("stores");
       console.log(results);
     }
    
  4. The full application looks like:

    const fs = require("fs").promises;
    
    async function findSalesFiles(folderName) {
    
      // (1) Add an array at the top, to hold the paths to all the sales files that the program finds.
      let results = [];
    
      // (2) Read the currentFolder with the `readdir` method. 
      const items = await fs.readdir(folderName, { withFileTypes: true });
    
      // (3) Add a block to loop over each item returned from the `readdir` method using the asynchronous `for...of` loop. 
      for (const item of items) {
    
        // (4) Add an `if` statement to determine if the item is a file or a directory. 
        if (item.isDirectory()) {
    
          // (5) If the item is a directory, recursively call the function `findSalesFiles` again, passing in the path to the item. 
          const resultsReturned = await findSalesFiles(`${folderName}/${item.name}`);
          results = results.concat(resultsReturned);
        } else {
          // (6) If it's not a directory, add a check to make sure the item name matches *sales.json*.
          if (item.name === "sales.json") {
            results.push(`${folderName}/${item.name}`);
          }
        }
      }
    
      return results;
    }
    
    async function main() {
      const results = await findSalesFiles("stores");
      console.log(results);
    }
    
    main();
    

Run the program

  1. Enter the following command at the terminal to run the program.

    node index.js
    
  2. The program should show the following output.

    [
     'stores/201/sales.json',
     'stores/202/sales.json',
     'stores/203/sales.json',
     'stores/204/sales.json',
    ]
    

Excellent! You've successfully written a command-line program that can traverse any directory and find all the sales.json files inside.

However, the way that the path to subfolders was constructed in this example is a little clumsy because it requires concatenating strings together. Also, you might run into issues on other operating systems (like Windows) that use different path separators.

In the next section, you'll learn how to construct paths that work across operating systems by using the path module.

Got stuck?

If you got stuck at any point in this exercise, here's the completed code. Remove everything in index.js and replace it with this solution.

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

async function findSalesFiles(folderName) {

  // (1) Add an array at the top, to hold the paths to all the sales files that the program finds.
  let results = [];

  // (2) Read the currentFolder with the `readdir` method. 
  const items = await fs.readdir(folderName, { withFileTypes: true });

  // (3) Add a block to loop over each item returned from the `readdir` method using the asynchronous `for...of` loop. 
  for (const item of items) {

    // (4) Add an `if` statement to determine if the item is a file or a directory. 
    if (item.isDirectory()) {

      // (5) If the item is a directory, recursively call the function `findSalesFiles` again, passing in the path to the item. 
      const resultsReturned = await findSalesFiles(`${folderName}/${item.name}`);
      results = results.concat(resultsReturned);
    } else {
      // (6) If it's not a directory, add a check to make sure the item name matches *sales.json*.
      if (item.name === "sales.json") {
        results.push(`${folderName}/${item.name}`);
      }
    }
  }

  return results;
}

async function main() {
  const results = await findSalesFiles("stores");
  console.log(results);
}

main();