14

I'm using Multer to upload images in Express 4. However, the examples all show Multer being defined in the express file as Middleware. I'd like to actually define some of the Multer behaviors in my app routing itself. Is this possible? The end result that I need is for my route function to recognize when the upload is finished before it sends the server response to the browser, so an image can be displayed to the user (right now I'm only getting a partial image displayed because the file hasn't finished uploading yet).

CURRENT, WORKING CODE

express.js

// Require Multer as module dependency.
var multer = require('multer');

// Using Multer for file uploads.
app.use(multer({
    dest: './public/profile/img/',
    limits: {
        fieldNameSize: 50,
        files: 1,
        fields: 5,
        fileSize: 1024 * 1024
    },
    rename: function(fieldname, filename) {
        return filename;
    },
    onFileUploadStart: function(file) {
        if(file.mimetype !== 'image/jpg' && file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {
            return false;
        }
    }
}));

server_routes.js

app.route('/users/image').post(server_controller_file.imageUpload);

server_controller_file.js

exports.imageUpload = function(req, res) {
// Check to make sure req.files contains a file, mimetypes match, etc., then send appropriate server response.
};

Ideally, my server_controller_file.js would contain some checks to make sure the file finished uploading, e.g. (note: this is hypothetical/desirable, not actual working code)...

var multer = require('multer');
exports.imageUpload = function(req, res) {
    multer({
        onFileUploadComplete: function(file) {
            res.send();
        }
    });
}

Again, right now the async nature of node is causing the browser to think the upload is complete as soon as it receives a successful response, so when I update the url to display the image, it only partially displays. Thanks for the help!

aikorei
  • 570
  • 1
  • 7
  • 23
  • Can u get `file.path` in onFileUploadComplete function? – Ryan Yiada Jan 06 '15 at 00:50
  • Because `multer` works as Middleware, I think it would be not workning in your server_controller_file.So you don't know when it has completed upload file. See this example:http://codeforgeek.com/2014/11/file-uploads-using-node-js/ – Ryan Yiada Jan 06 '15 at 00:55
  • @RyanYiada I can access file.path in the onFileUploadComplete function, but it still doesn't communicate with my router. The article you linked to was good - it has a variable 'done' that's in scope for both the middleware and the router, but since my middleware and router are in separate files, my middleware variables are out of scope for the router. – aikorei Jan 06 '15 at 15:49
  • @aikorei, express.js was main server file right? – sankar muniyappa Apr 07 '17 at 15:10
  • @shankarmsr no, there is a separate server.js file that kicks everything off, and includes the express.js file. – aikorei Apr 09 '17 at 13:17

3 Answers3

18

Actually you can do what you want with another method:

var express = require('express');
var multer  = require('multer');
var upload = multer({ dest: './uploads/'});
var app = express();

app.get('/', function(req, res){
  res.send('hello world');
});

// accept one file where the name of the form field is named photho
app.post('/', upload.single('photho'), function(req, res){
    console.log(req.body); // form fields
    console.log(req.file); // form files
    res.status(204).end();
});

app.listen(3000);
Sagar V
  • 12,158
  • 7
  • 41
  • 68
Aymen Mouelhi
  • 2,384
  • 2
  • 18
  • 16
  • 2
    This is a better approach then compared to the solution marked as answer. it optimizes the middleware per route. If you chain multiple middleware together, you will find out, that express uses streams, but this does not fully work with middleware. Events are not piped between middleware. If you use two express middlewares in a chain and each tries to catch all stream events and calls next() when all the eventing is done, the second middleware will not receive the required events anymore. In such a case you need to optimize the middleware per route. – SharpCoder Jun 15 '15 at 06:38
  • Just a quick question why is it encased in [ ], is this like defining a chain rather then using the next function? – Snymax Jul 11 '15 at 13:04
  • 1
    Your answer will not work if destination folder name will depend on req.body parameters, how will you do it then ??? – Sudhanshu Gaur Sep 06 '15 at 03:04
  • 1
    What if I want to upload through a rest api call. What should I pass to single() function? – Bagusflyer Feb 05 '16 at 06:46
  • I'm trying to do precisely what @SudhanshuGaur mentioned. I'm using multer to handle an image upload as part of a form but need to rename the image to the value of another input within the form. Can't seem to pass a new filename to multer after the req.body variable has been populated. – Meelah Mar 08 '16 at 17:26
7

OK, I actually just ended up writing the raw data. If you set inMemory to true, it sends the raw data to req.files.file.buffer. Here's the final, working solution:

express.js

// Using Multer for file uploads.
app.use(multer({
    dest: './public/profile/img/',
    limits: {
        fieldNameSize: 50,
        files: 1,
        fields: 5,
        fileSize: 1024 * 1024
    },
    rename: function(fieldname, filename) {
        return filename;
    },
    onFileUploadStart: function(file) {
        console.log('Starting file upload process.');
        if(file.mimetype !== 'image/jpg' && file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {
            return false;
        }
    },
    inMemory: true //This is important. It's what populates the buffer.
}));

server_controller_file.js

exports.imageUpload = function(req, res) {
    var file = req.files.file,
        path = './public/profile/img/';

    // Logic for handling missing file, wrong mimetype, no buffer, etc.

    var buffer = file.buffer, //Note: buffer only populates if you set inMemory: true.
        fileName = file.name;
    var stream = fs.createWriteStream(path + fileName);
    stream.write(buffer);
    stream.on('error', function(err) {
        console.log('Could not write file to memory.');
        res.status(400).send({
            message: 'Problem saving the file. Please try again.'
        });
    });
    stream.on('finish', function() {
        console.log('File saved successfully.');
        var data = {
            message: 'File saved successfully.'
        };
        res.jsonp(data);
    });
    stream.end();
    console.log('Stream ended.');
};
eephillip
  • 1,183
  • 15
  • 25
aikorei
  • 570
  • 1
  • 7
  • 23
2

I find an example for busboy:

exports.upload = function (req, res, next) {
   req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
       // ....
   });

   req.pipe(req.busboy);
};

multer is also pipe a busboy:

 req.pipe(busboy);

https://github.com/expressjs/multer/blob/master/index.js#206

Ryan Yiada
  • 4,739
  • 4
  • 18
  • 20
  • Are you saying that doing what I want to do won't work with Multer? And that I should use Busboy instead? – aikorei Jan 06 '15 at 16:09
  • It wasn't clear to me until I looked under the hood, but multer is just busboy wrapped up and implemented as middleware. I needed to hook into the events of busboy in the route, so it made sense to just use busboy directly. – eephillip Jan 11 '15 at 15:36
  • @aikorei as eephillip says,multer is just busboy wrapper.multer will return busboy. – Ryan Yiada Jan 12 '15 at 03:21