user4445419
user4445419

Reputation:

Extract zip into folder node via express

I try to find example where I can send a zip (like via postman) and get this zip in my handler and unzip it so specified folder I didn't find much examples for zipping using express I want to unzip it in path web/app

I try something like the following which doesn't works for me , the zip file is not unzipped in the specified folder, any idea what im doing wrong ?

https://nodejs.org/api/zlib.html#zlib_zlib

var zlib = require('zlib');
var fs = require('fs');
const dir = path.join(__dirname, 'web/app/');

if (req.file.mimetype === 'application/zip') {

    var unzip = zlib.createUnzip();

    var read = fs.createReadStream(req.file);
    var write = fs.createWriteStream(dir);
    //Transform stream which is unzipping the zipped file
    read.pipe(unzip).pipe(write);   
    console.log("unZipped Successfully");

}

Any working example will be very helpful, or reference where can I've problem...

while debug I see the that this is when the code failed

var read = fs.createReadStream(req.file);

any idea why?

I've also tried with

var read = fs.createReadStream(req.file.body);

the issue that I dont see the error, reason etc.

when I change it to

var read = fs.createReadStream(req.file.buffer);

the program doesnt exit and i was able to run it until the logger console.log("unZipped Successfully"); but nothing happen...

if there any example with https://www.npmjs.com/package/yauzl yauzl and multer in my context it will be great

update- this is the postman request

enter image description here

Upvotes: 12

Views: 11470

Answers (4)

REDDY PRASAD
REDDY PRASAD

Reputation: 1409

First of all, zlib does not support extracting zip files.

I recommend formidable for handling files because

  1. its battle tested
  2. most widely used
  3. avoids writing boilerplate plate code like reading filestream from request, storing and handling errors
  4. easily configurable

Prerequisites
Install dependencies using npm i -S extract-zip formidable express or yarn add extract-zip formidable express

Bare minimal solution for your problem with formidable and extract-zip

const express = require('express');
const fs = require('fs');
const extract = require('extract-zip')
const formidable = require('formidable');
const path = require('path');
const uploadDir = path.join(__dirname, '/uploads/');
const extractDir = path.join(__dirname, '/app/');
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir);
}
if (!fs.existsSync(extractDir)) {
  fs.mkdirSync(extractDir);
}

const server = express();

const uploadMedia = (req, res, next) => {
  const form = new formidable.IncomingForm();
  // file size limit 100MB. change according to your needs
  form.maxFileSize = 100 * 1024 * 1024;
  form.keepExtensions = true;
  form.multiples = true;
  form.uploadDir = uploadDir;

  // collect all form files and fileds and pass to its callback
  form.parse(req, (err, fields, files) => {
    // when form parsing fails throw error
    if (err) return res.status(500).json({ error: err });

    if (Object.keys(files).length === 0) return res.status(400).json({ message: "no files uploaded" });
    
    // Iterate all uploaded files and get their path, extension, final extraction path
    const filesInfo = Object.keys(files).map((key) => {
      const file = files[key];
      const filePath = file.path;
      const fileExt = path.extname(file.name);
      const fileName = path.basename(file.name, fileExt);
      const destDir = path.join(extractDir, fileName);

      return { filePath, fileExt, destDir };
    });

    // Check whether uploaded files are zip files
    const validFiles = filesInfo.every(({ fileExt }) => fileExt === '.zip');

    // if uploaded files are not zip files, return error
    if (!validFiles) return res.status(400).json({ message: "unsupported file type" });

    res.status(200).json({ uploaded: true });

    // iterate through each file path and extract them
    filesInfo.forEach(({filePath, destDir}) => {
      // create directory with timestamp to prevent overwrite same directory names
      extract(filePath, { dir: `${destDir}_${new Date().getTime()}` }, (err) => {
        if (err) console.error('extraction failed.');
      });
    });
  });

  // runs when new file detected in upload stream
  form.on('fileBegin', function (name, file) {
    // get the file base name `index.css.zip` => `index.html`
    const fileName = path.basename(file.name, path.extname(file.name));
    const fileExt = path.extname(file.name);
    // create files with timestamp to prevent overwrite same file names
    file.path = path.join(uploadDir, `${fileName}_${new Date().getTime()}${fileExt}`);
  });
}

server.post('/upload', uploadMedia);

server.listen(3000, (err) => {
  if (err) throw err;
});

This solution works for single/multiple file uploads. The one problem with this solution is, wrong file types will get uploaded to uploaded directory though server throw error.

To test with postman: postman image

Upvotes: 8

Ihor
Ihor

Reputation: 53

This is my code for uploading a file to express server.

//require express library
var express = require('express');
//require the express router
var router = express.Router();
//require multer for the file uploads
var multer = require('multer');

//File Upload

var storage = multer.diskStorage({
  // destino del fichero
  destination: function (req, file, cb) {
    cb(null, './uploads/logo')
  },
  // renombrar fichero
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  }
});

var upload = multer({ storage: storage });

router.post("/", upload.array("uploads[]", 1), function (req, res) {
  res.json('Uploaded logo successfully');
});


module.exports = router; 

Upvotes: -1

coockoo
coockoo

Reputation: 2392

Prerequisites:

  1. npm i express unzipper multiparty bluebird
  2. Create app/web directory in your project root (or you can automate creation if you want).
  3. Place all of these files into one directory.
  4. Node version that supports async/await (7.6+ as far as I know)

server.js:

const express = require('express');
const Promise = require('bluebird');
const fs = require('fs');
const writeFile = Promise.promisify(fs.writeFile);

const { parseRequest, getFile } = require('./multipart');
const { extractFiles } = require('./zip')

const EXTRACT_DIR = 'web/app';

const app = express();

const uploadFile = async (req, res, next) => {
  try {
    const body = await parseRequest(req);
    const bodyFile = getFile(body, 'file');
    if (!/\.zip$/.test(bodyFile.originalFilename)) {
      res.status(200).json({ notice: 'not a zip archive, skipping' })
      return;
    }
    const archiveFiles = await extractFiles(bodyFile);

    await Promise.each(archiveFiles, async (file) => {
      await writeFile(EXTRACT_DIR + '/' + file.path, file.buffer);
    })
    res.status(200).end();
  } catch (e) {
    res.status(500).end();
  }
};

app.post('/files', uploadFile);

app.listen(3000, () => {
  console.log('App is listening on port 3000');
});

multipart.js

const Promise = require('bluebird');
const { Form } = require('multiparty');

function parseRequest (req, options) {
    return new Promise((resolve, reject) => {
        const form = new Form(options)
        form.parse(req, (err, fields, files) => {
            if (err) {
                return reject(err);
            }
            return resolve({ fields, files });
        });
    });
}

function getFile (body, field) {
    const bodyFile = body.files[field];
    const value = bodyFile ? bodyFile[0] : null;
    return value || null;
}

module.exports = {
    parseRequest,
    getFile,
};

zip.js

const unzip = require('unzipper');
const fs = require('fs');

async function extractFiles (file) {
    const files = [];
    await fs.createReadStream(file.path).pipe(unzip.Parse()).on('entry', async entry => {
    // Cleanup system hidden files (or drop this code if not needed)
        if (
            entry.type !== 'File'
            || /^__MACOSX/.test(entry.path)
            || /.DS_Store/.test(entry.path)
        ) {
            entry.autodrain()
            return
        }
        const pathArr = entry.path.split('/');
        const path = entry.path;
        const buffer = await entry.buffer();
        files.push({ buffer, path, originalFilename: pathArr[pathArr.length - 1] });
    }).promise();
    return files;
}

module.exports = {
    extractFiles,
};

Usage:

  1. Start a server with node server
  2. Send your file in file field in request (key file in postman). Example in curl curl -XPOST -F 'file=@../ttrra-dsp-agency-api/banner.zip' 'localhost:3000/files')

Downsides:

  1. Unzipped files are stored in buffer so this method doesn't work great and is not recommended for big archives.

Upvotes: 2

jjbskir
jjbskir

Reputation: 10743

Without a full example it's tough to say what the real problem is. But according to Express docs it says:

In Express 4, req.files is no longer available on the req object by default. To access uploaded files on the req.files object, use multipart-handling middleware like busboy, multer, formidable, multiparty, connect-multiparty, or pez.

So if you are not using a middleware library to handle uploading files, it's tough to tell what the value of req.file is.

I am also a bit worried that you are trying to use zlib to decompress a zip file, since the library only supports gzip.

The zlib module provides compression functionality implemented using Gzip and Deflate/Inflate

You would want to check for req.file.mimetype === 'application/gzip'

Here are some posts related to unzipping zip files:

Upvotes: 2

Related Questions