Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

NodeJS Folder Structure Builder - Need to run it multiple times?

Hi all,

This is my first node application.

The purpose of the application is create a folder structure in a share drive, the structure is different per company which is why I have decided to use node. Hopefully the code explains itself.

For some reason I have to run the app multiple times so that it builds the entire structure, I want it to work after running it once.

"use strict";

console.time("execTime");

const fs = require("fs");

// Defines the base directory for where the folders will be created
const root = "/Users/Rhys/Desktop/Test/";

// An array for the company objects which includes the company name,
// the products and how far the back the data should go
let companies = [
    {
        name: "Company A",
        products: [
            "Pension Auto-Enrolled",
            "Sick Pay",
            "PMI"
        ],
        minimumYear: 2016
    },
    {
        name: "Company B",
        products: [
            "Pension Auto-Enrolled",
            "Pension Contractual",
            "Sick Pay"
        ],
        minimumYear: 2016
    },
    {
        name: "Company C",
        products: [
            "Pension Contractual",
            "Sick Pay"
        ],
        minimumYear: 2016
    }

];


// This contains an array of all the folders inside the pension product
const pensionArray = [
    "Assessment Files",
    "IO Uploads",
    "Joiner Files",
    "Literature/Packs",
    "Literature/Provider",
    "Literature/Regular Letters",
    "Literature/Templates",
    "Literature/Regular Letters/Postponement",
    "Literature/Regular Letters/Eligible",
    "Literature/Regular Letters/Existing",
    "Literature/Regular Letters/Initial",
    "Literature/Regular Letters/Non-Eligible",
    "Opt-Outs",
    "Opt-Outs/Forms",
    "Opt-Outs/Reports",
    "Payment Files",
    "Projects",
    "Received Files",
    "Received Files/Company",
    "Received Files/Provider"
];

// An array of the folders under the risk products (Sick Pay, Travel Insurance etc.)
const shortRiskArray = [
    "Literature",
    "Literature/Membership Apps",
    "Literature/Scheme",
    "New Business",
    "New Business/Client Communications",
    "New Business/Final Accounts & Invoice",
    "New Business/Long Term Absencete",
    "New Business/Management Info",
    "New Business/Membership Listings",
    "New Business/Policy Particulars",
    "New Business/Quotes",
    "New Business/Reports",
    "Renewals",
    "Reports",
];

// An array of the extra folders for the more complicated risk products (PMI, Group Life etc.)
const fullRiskArray = [
    "Claims",
    "Leavers & Joiners",
    "Misc",
    "Tupe",
    ...shortRiskArray
];

// An array of folders that should have year and month structure in them (Small Risk Products)
const dateFolderShortRiskArray = [
    "Renewals",
    "Reports"
];

// An array of folders that should have year and month structure in them (Full Risk Products)
const dateFolderFullRiskArray = [
    "Claims",
    "Leavers & Joiners",
    "Tupe",
    ...dateFolderShortRiskArray
];

// An array of folders that should have year and month structure in them (Pension Product)
const dateFolderPensionArray = [
    "Assessment Files",
    "IO Uploads",
    "Literature/Packs",
    "Literature/Regular Letters/Eligible",
    "Literature/Regular Letters/Existing",
    "Literature/Regular Letters/Initial",
    "Literature/Regular Letters/Non-Eligible",
    "Literature/Regular Letters/Postponement",
    "Opt-Outs/Forms",
    "Opt-Outs/Reports",
    "Payment Files/Monthly",
    "Payment Files/Weekly",
    "Received Files/Company",
    "Received Files/Provider"
];

// A function that takes folder path and adds the base directory before creating the folder
function createFolder(folderPath){

    let path = root + folderPath + "/";
    fs.mkdir(path, function(event, err){
        if (event === null) {
            console.log("Created a folder at " + path);
        }
    });

}

// A function that creates the year and month folder structure
// Takes the path to the product
// Takes the year for how far back the data should go
// Takes an array of directories that should have the year and date structure
function createDateFolders(folderPath, minimumYear, productArray){

    const monthArray = [
        "01 Jan",
        "02 Feb",
        "03 Mar",
        "04 Apr",
        "05 May",
        "06 Jun",
        "07 Jul",
        "08 Aug",
        "09 Sep",
        "10 Oct",
        "11 Nov",
        "12 Dec"
    ];

    // Loops through all the directories
    productArray.forEach(function(currentPath){

        // Get's the current year
        const thisYear = new Date().getFullYear();

        // Loops through each year until it's hits the minimumYear
        for (let i = thisYear; i >= minimumYear; i -= 1){
            let path = folderPath + "/" + currentPath + "/";
            createFolder(path + i);

            // Adds all the months into the year
            monthArray.forEach(function(month){
                createFolder(path + i + "/" + month);
            })
        }
    })

}

// The main function to be called
function buildCompanies () {

    // Loops through all the companies
    companies.forEach(function(company){

        // Creates the parent folder
        createFolder(company.name);

        // If the product is in the companies products array then create folder structure
        if (company.products.includes("Critical Illness") === true) {

            // Creates the product folder under the company folder
            let currentPath = company.name + "/Critical Illness";
            createFolder(currentPath);

            // Creates all the folders under the product
            fullRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            //Creates all the year and month folders under the required folders
            createDateFolders(currentPath, company.minimumYear, dateFolderFullRiskArray);

        }

        if (company.products.includes("Dental") === true) {

            let currentPath = company.name + "/Dental";
            createFolder(currentPath);

            shortRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderShortRiskArray);

        }

        if (company.products.includes("Group Income Protection") === true) {

            let currentPath = company.name + "/Group Income Protection";
            createFolder(currentPath);

            fullRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderFullRiskArray);

        }

        if (company.products.includes("Group Life") === true) {

            let currentPath = company.name + "/Group Life";
            createFolder(currentPath);

            fullRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderFullRiskArray);

        }

        if (company.products.includes("Health Assessments") === true) {

            let currentPath = company.name + "/Health Assessments";
            createFolder(currentPath);

            shortRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderShortRiskArray);

        }

        if (company.products.includes("Pension Auto-Enrolled") === true) {

            let currentPath = company.name + "/Pension";
            createFolder(currentPath);

            currentPath += "/Auto-Enrolment";
            createFolder(currentPath);

            pensionArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderPensionArray);

        }

        if (company.products.includes("Pension Contractual") === true) {

            let currentPath = company.name + "/Pension";
            createFolder(currentPath);

            currentPath += "/Contractual";
            createFolder(currentPath);

            pensionArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderPensionArray);

        }

        if (company.products.includes("PMI") === true) {

            let currentPath = company.name + "/PMI";
            createFolder(currentPath);

            fullRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderFullRiskArray);

        }

        if (company.products.includes("Sick Pay") === true) {

            let currentPath = company.name + "/Sick Pay";
            createFolder(currentPath);

            shortRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderShortRiskArray);

        }

        if (company.products.includes("Travel Insurance") === true) {

            let currentPath = company.name + "/Travel Insurance";
            createFolder(currentPath);

            shortRiskArray.forEach(function (path) {
                createFolder(currentPath + "/" + path);
            });

            createDateFolders(currentPath, company.minimumYear, dateFolderShortRiskArray);

        }
    })

}

// Calls the main function
buildCompanies();

console.timeEnd("execTime");

1 Answer

Seth Kroger
Seth Kroger
56,413 Points

While a lot of languages/runtimes will wait for a filesystem operation to finish before returning, node will return immediately and use the callback when the operation is done. Think of it as making a request to do something and getting a reply back versus doing something until it's done.

You see when you make a directory and then try to make subdirectories inside it, the latter requests happen immediately after the first, but the first hasn't been processed yet. The directory you're making the subdirectories in doesn't exist yet, which causes an error. You either need to wait for the callback and create the new subdirectories there or use the ends in "Sync" versions of the methods that work synchronously instead of asynchronously.

Thanks for your reply Seth was very informative.

I tried using the synchronous method mkdirSync which still didn't work. I am guessing I need to make the functions that call the createFolder function synchronous, I am not sure how to do that?

I have found an npm package called mkdir-recursive which will create any folder in the path that doesn't exist. My app now works how I want it to, my only concern now is this app is inefficient. I believe the best way to write the code would be to run each company asynchronously but the folder structure under the company synchronously. Unfortunately that is beyond my knowledge, please could you point me in the right direction.