Multer create new folder with data

user4209821 picture user4209821 · Jan 4, 2016 · Viewed 11.7k times · Source

I use multer.

Question 1

When I put the following snippet in the app.js

app.use(multer({
        dest: './uploads'
    }
).single('file'));

it creates a new folder under the root folder, my question is about this new folder's lifeCycle, When it'll be deleted? How much the size of the folder could be after 100 call?

Question 2

If I don't want to limit the file size, what I should put in the configuration?

app.use(multer({
    dest: './public/profile/img/',
    limits: {
        fieldNameSize: 50,
        files: 1,
        fields: 5,
        fileSize: 1024 * 1024
    },

Update

My app is built like

app.js file contain

    app.use(multer({
            dest: './uploads'
        }
    ).single('file'));

app.use('/', routes, function (req, res, next) {
    next();
});

The route file look like following

appRouter
    .post('*', function (req, res) {
        handler.dispatch(req, res)
    })
    .get('*', function (req, res) {
        handler.dispatch(req, res)
    })

And in third file I use the unzip like following

update: function (req, res) {
  var filePath = path.join(req.file.destination, req.file.filename);
            var unzipper = new Unzipper(filePath);
            unzipper.on("extract", function () {
                console.log("Finished extracting");
                res.sendStatus(200);
            });
            unzipper.on('progress', function (fileIndex, fileCount) {
                console.log('Extracted file ' + (fileIndex + 1) + ' of ' + fileCount);
            });
            unzipper.on('list', function (files) {
                console.log('The archive contains:');
                console.log(files);
            });

            unzipper.on('error', function (err) {
                console.log('Caught an error', err);
            });

            unzipper.extract({
                path: "./"
            });
        }

The below is how my node Application is structured, can someone please advice how and where(which file) its recommended to use the Raf code with adding a dateTime to the file which I can add sorting ...

Answer

Raf picture Raf · Jan 22, 2016

I will try to answer your question with a real life example, at least you might learn some stuff from it. If you wish to delete all except the most recent upload, then you need to code some sort of logic to differentiate between which upload is recent and which ones are old. Below I describe, how I would go about solving this issue, might not be perfect but, that is how I do it.

The folder will never be deleted automatically, unless you delete it either manually or programmatically.

The size of folder with 100 calls assuming that in each call you upload a file of x size would be x multiplied by 100

You don't want to limit the file upload, don't supply limits configs, however it is recommended to specify a file upload limit.

You can obviously attach the multer to the app or create an instance of it and pass that to a route. I prefer the second method:

multer config

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    var filename = file.originalname;
    var fileExtension = filename.split(".")[1];
    cb(null, Date.now() + "." + fileExtension);
  }
}); 

As shown above, I do not let multer give a random name to the uploaded file. What I do is, get the file name, strip off its extension and then use Date.now() which will give me the current timestamp appended with the uploaded file extension. If I make six uploads, they would show as follow (most of my uploads were .jpg, that's taken from filename).

How upload would end up (timestamps would differ)

1453414099665.jpg (oldest) 
1453414746114.JPG
1453414748991.jpg
1453414751662.jpg
1453414754815.jpg (most recent)

I attached the above storage to an instance of multer as follow:

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

Now I can pass the upload to a route which handle the file upload as follow:

attach upload to route as shown below

//simple route to return upload form, simple jade file
app.get('/upload', function(req, res){
  res.render('upload');
});

//this route processes the upload request, see below upload.single('file') 
//is the passed multer
app.post('/upload', upload.single('file'),  function(req,res){
  res.status(204).end();
});

Let's say you keep uploading and then at some point you want to list all files in the uploads directory. The route would be as follow:

List all files in uploads directory

//lists all files in the uploads directory and return it to browser as json response
app.get('/listAllFiles', function(req, res) {
  //reading directory in synchronous way
  var files = fs.readdirSync('./uploads');
  res.json(files);
});

You want to delete all files in the upload directory, the route would look as follow:

Delete all files in uploads directory

//delete all files in the upload direcotry asynchronously
app.get('/deleteAllFiles', function(req, res) {
  fs.readdir('./uploads', function(err, items) {
    items.forEach(function(file) {
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
    });
    res.status(204).end();
  });
});

If you wish to delete all files synchronously then you have to invoke the sync version of readdir (readdirSync) and unlink (unlinkSync)

var filenames = fs.readdirSync('./uploads');

filenames.forEach(function(file) {
  fs.unlinkSync('./uploads/' + file);
});

Now to your point of deleting all but, the most recent uploaded file. Well, I have already made all filename be timestamps. So I would do something as follow:

Delete all files except most recent (where most recent being the one with most recent timestamp as its filename).

//delets all file asynchronously except the most recent in which case the file
//with name being the latest timestamp is skipped.
app.get('/deleteAllExceptMostRecent', function(req, res) {
  console.log('/deleteAllFilesExceptMostRecent');
  fs.readdir('./uploads', function(err, items) {
    //sort the array of files names in reverse, so we have most recent file on top
    items.reverse();
    var flag = true;

    items.forEach(function(file) {
      //skip deletion of most recent file. if condition executed onces only.
      if(flag) {
        flag = false;
      } else {
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
      }
    });
  });
  res.status(204).end();
});

I have not added any limits in my example but, it is recommended. The default file size limit is infinity and if you don't include it in a prod environment then you will be vulnerable to DoS attacks as indicated in the comments.

For the above file operation to work you need to load

var fs = require('fs'); 

In regards to your second point, simply skip the limits property and the default limit will be infinity.

For demonstration purposes, I have setup the above in a working nodejs app, see below:

app.js

var express = require('express');
var multer = require('multer');
var bodyParser = require('body-parser');
var path = require('path');
var fs = require('fs');

var app = new express();
app.use(bodyParser.json());

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    /* if you need to retain the original filename, then use filename and append to it date
     * if you don't need original filename but, just extension, then append extension at the
     * end of current timestamp. If you don't need extenion then just use Date.now() which
     */
    var filename = file.originalname;
    var fileExtension = filename.split(".")[1];

    cb(null, Date.now() + "." + fileExtension);
  }
})

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

//get upload form
app.get('/upload', function(req, res){
  res.render('upload');
});

//process upload
app.post('/upload', upload.single('file'),  function(req,res){
  res.status(204).end();
});

//lists all files in the uploads directory.
app.get('/listAllFiles', function(req, res) {
  var files = fs.readdirSync('./uploads');
  res.json(files);
});

//delete all files in the upload direcotry asynchronously
app.get('/deleteAllFiles', function(req, res) {
  fs.readdir('./uploads', function(err, items) {
    items.forEach(function(file) {
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
    });
    res.status(204).end();
  });
});

//delets all file asynchronously except the most recent in which case the file
//with name being the latest timestamp is skipped.
app.get('/deleteAllExceptMostRecent', function(req, res) {
  console.log('/deleteAllFilesExceptMostRecent');
  fs.readdir('./uploads', function(err, items) {
    items.reverse();
    var flag = true;

    items.forEach(function(file) {
      if(flag) {
        flag = false;
      } else {
        fs.unlink('./uploads/' + file);
        console.log('Deleted ' + file);
      }
    });
  });
  res.status(204).end();
});

//delete all files of a direcotry in synchronous way
app.get('/deleteAllSync', function(req, res) {
  var filenames = fs.readdirSync('./uploads');

  filenames.forEach(function(file) {
    fs.unlinkSync('./uploads/' + file);
  });
});

//delete all files except most recent in synchronous way
app.get('/deleteAllSyncExceptMostRecent', function(req, res) {
  var filenames = fs.readdirSync('./uploads');
  filenames.reverse();
  var flag = true;
  filenames.forEach(function(file) {
    if(flag) 
      flag = false;
    else
      fs.unlinkSync('./uploads/' + file);
  });
});

var port = 3000;
app.listen( port, function(){ console.log('listening on port '+port); } );

views/upload.jade

html
  head
    title
  body
    form(method="post",enctype="multipart/form-data",action="/upload")
      p
        input(type="file",name="file")
      p
        input(type="submit")