How to properly download a .zip file using Node Js, Archiver and Express

mgperkowski picture mgperkowski · Apr 15, 2016 · Viewed 7.6k times · Source

I am attempting to zip the contents of two directories and download the resulting .zip file. One directory contains .txt files and the other .jpg. I am using archiver to zip the files and running the express framework on node js. I am inclined to think that the problem exists in the download step, as the resulting zipped file that is created in the project root expands as expected, however when the file is downloaded, I get an "Error 2 - No such file or directory."

    app.get('/download',function(req, res){

        zipFile = new Date() + "-Backup.zip";

        var output = fs.createWriteStream(__dirname +"/backups/"+ zipFile);
        var archive = archiver('zip');

        output.on('close', function() {
            console.log(archive.pointer() + ' total bytes');
            console.log('archiver has been finalized and the output file descriptor has closed.');
        });

        archive.on('error', function(err) {
            throw err;
        });

        archive.pipe(output);

        var files1 = fs.readdirSync(__dirname+'/posts');
        var files2 = fs.readdirSync(__dirname+'/uploads');
            for(var i = 0; i< files1.length; i++){
                archive.append(fs.createReadStream(__dirname+"/posts/"+files1[i]), { name: files1[i] });
            }
            for(var i = 0; i< files2.length; i++){
                archive.append(fs.createReadStream(__dirname+"/uploads/"+files2[i]), { name: files2[i] });
            }
        archive.finalize(); 
        res.download(__dirname + "/backups/" + zipFile, zipFile); 

    });

zipFile is a global variable.

The on 'close' logs fire properly and no errors occur, but the file will not open after being downloaded. Is there an issue with response headers or something else I am unaware of?

Thanks for the help.

Answer

mgperkowski picture mgperkowski · Jul 21, 2016

I solved my own problem using node-zip as the archive utility.

var zip = require('node-zip')(); // require the node-zip utility
var fs = require('fs'); // I use fs to read the directories for their contents

var zipName = "someArbitraryName.zip"; // This just creates a variable to store the name of the zip file that you want to create
var someDir = fs.readdirSync(__dirname+"/nameOfDirectoryToZip"); // read the directory that you would like to zip
var newZipFolder = zip.folder('nameOfDirectoryToZip'); // declare a folder with the same name as the directory you would like to zip (we'll later put the read contents into this folder)


//append each file in the directory to the declared folder
for(var i = 0; i < someDir.length,i++){
    newZipFolder.file(someDir[i], fs.readFileSync(__dirname+"/nameOfDirectoryToZip/"+someDir[i]),{base64:true});
}

var data = zip.generate({base64:false,compression:'DEFLATE'}); //generate the zip file data

//write the data to file
fs.writeFile(__dirname +"/"+ zipName, data, 'binary', function(err){
    if(err){
        console.log(err);
    }
    // do something with the new zipped file
}

Essentially, what is happening can broken down into 3 steps:

  1. Use fs to read the contents of a directory that you would like to zip.
  2. Use zip.folder to declare the folder, then use zip.file to append files to that directory. I just used a for loop to iteratively add each file in the directory that was read in step 1.
  3. Use zip.generate to create the .zip file data, and write it to file using fs.

The resulting file can be downloaded or whatever you would like to do with it. I have seen no issues using this method.

If you want to zip more than one directory, just repeat steps 1 and 2 before you zip.generate, creating a new zip.folder for each directory.