Extract Zip files recursively with NodeJS

Hello Coders 👋

This is my first attempt at writing a technical post. Hope you find it helpful.

Problem Statement

Recently, I encountered a requirement where I had to extract all the zip files present inside a main zip file which had a random folder structure and any of the folders can have a zip file present inside it at any level.

Breaking down of the problem

  1. Find Library for extracting
  2. Extract the main zip file i.e demo.zip
  3. Figure out a way to traverse the whole folder structure recursively
  4. Then extract the .zip file whenever it is found.

Solution

Method to extract zip file

  • It takes two input arguments source and target. source should be absolute path of the zip file, target is where the folder will get extracted.
async function extractZip(source, target) {
  try {
    await extract(source, { dir: target });
    console.log("Extraction complete");
  } catch (err) {
    console.log("Oops: extractZip failed", err);
  }
}

Method to traverse folders recursively

const unzipFiles = async function (dirPath) {
  const files = fs.readdirSync(dirPath);

  await Promise.all(
    files.map(async (file) => {
      if (fs.statSync(dirPath + "/" + file).isDirectory()) {
        await unzipFiles(dirPath + "/" + file);
      } else {
        const fullFilePath = path.join(dirPath, "/", file);
        const folderName = file.replace(".zip", "");
        if (file.endsWith(".zip")) {
          zippedFiles.push(folderName);
          await extractZip(fullFilePath, path.join(dirPath, "/", folderName));
          await unzipFiles(path.join(dirPath, "/", folderName));
        }
      }
    })
  );
};

Lot of activities in above snippet. Let's decode

  • dirPath : file extraction path

  • The fs.readdirSync() method is used to synchronously read the contents of a given directory. The method returns an array with all the file names or objects in the directory.

  • Now, the main challenge was to loop through all the folders/files asynchronously. We cannot use forEach since it doesn't support async/await keyword. Traditional for loop syntax works with await keyword. But I wanted to use the more common array method map().

  • If you use await with map() it returns array of promises. Hence, to resolve all promises await Promise.all(arrayOfPromises) is used here.

  • For more details on async/await in loops refer to this wonderful article

if (fs.statSync(dirPath + "/" + file).isDirectory()) {
        await unzipFiles(dirPath + "/" + file);
      }
  • To check whether current object is file or folder isDirectory() method is used. If its a folder then call same method again i.e unzipFiles()
else {
        const fullFilePath = path.join(dirPath, "/", file);
        const folderName = file.replace(".zip", "");
        if (file.endsWith(".zip")) {
          zippedFiles.push(folderName);
          await extractZip(fullFilePath, path.join(dirPath, "/", folderName));
          await unzipFiles(path.join(dirPath, "/", folderName));
        }
  • If a file is found then we will call the extractZip() method with source and target with their absolute paths.

  • If we don't specify the target, or give it a current path then it will extract all the files in current directory itself. But I wanted to extract the zip to their respective folder names.

  • To achieve this, I have spliced the folder name from .zip file passed it as a target to extractZip() method.

  • Now there is one more catch in the last line i.e

await unzipFiles(path.join(dirPath, "/", folderName));
  • Since there is a possibility that the extracted files can also have zip files inside it, so as soon as we extract any file we will again have to call unzipFiles() to traverse through the extracted files.

Output will be -
Alt Text

Thanks a lot for reading 🙏

If you enjoyed this article or found it helpful, give it a thumbs-up 👍

Feel free to connect 👋

21