0

I am posting this issue here, I have been reading many related posts, but any of them helped me in making it work.

The idea is pretty easy: I have a MEAN-based application in which is possible to upload an image to GridFS - WITHOUT storing it locally to the server - and later to visualize it in the browser.

So let's start. I am uploading an image from AngularJS by using the angular-file-upload module. Therefore, in one of my AngularJS controller I have:

$scope.onFileSelect = function(file) {

   var fileName = file.name;

   $scope.upload = $upload.upload({
       url: '/file/uploadFile', //this is the node route for uploading the file
       headers: {
            'fileName':fileName
       },
       file: file
   }).
   success(function(data, status, headers, config) {
    //DOING STUFF
   }.
   error(function(data, status, headers, config) {
    //DOING OTHER STUFF
   });
}

In my Node + Express server, I am handling the request to upload and the request to download the image by means of the grids-stream module. This allows me to stream and save the image directly in/from GridFS without the need to save it in the server temporarily.

var mongoose = require('mongoose'),Grid = require('gridfs-stream'),gfs;

mongoose.connection.once('open',function() {
    gfs = Grid(mongoose.connection.db, mongoose.mongo);
});

exports.uploadFile = function(req, res) {
   var fileNameHeader = req.headers['filename'];
   //creating a unique filename
   var name = uuid.v1();
   var extIndex = fileNameHeader.lastIndexOf('.');
   var extension = fileNameHeader.substr(extIndex);
   var fileName = name+extension;

   var writeStream  = gfs.createWriteStream({
       filename: fileName,
       root: 'imagesGrid'
   });
   req.pipe(writeStream);
   writeStream.on('close',function() {
      //DOING STUFF 
   });
}

exports.downloadFile = function(req,res) {  
   var name = req.params.name;
   var readStream = gfs.createReadStream({
      filename: name,
      root: 'imagesGrid'
   });
   res.header({'Content-type': mime.lookup(name)});
   readStream.pipe(res);
};

On the client side, I have this HTML directive that generates all HTML image elements:

<my-image-draggable ng-repeat="image in userImages">
</my-image-draggable> 

where my-image-draggable has, as one of its elements:

<img ng-src='{{image.imageId.location}}'>

At runtime, {{image.imageId.location}}, is translated to (for instance): '/file/getFile/2350bcf0-53b4-11e4-9f6b-9fc9a66908c4.jpg', thus, in principle

<img ng-src='/file/getFile/2350bcf0-53b4-11e4-9f6b-9fc9a66908c4.jpg'>

should fire a HTTP GET request to downloadFile in the server, the image should be streamed from GridFS, and finally displayed in the browser. The problem is that the image is not displayed.

I have also tried to pre-process, in the Angular controller, the image data sent by the server in order to encode it to base64 and let at runtime ng-src be something like:

'data:image/jpeg;base64,'+Base64.encode(data);  

but it does not work neither.

I have also tried to call the downloadFile route manually and it gives no result. I cannot figure it out why this is not working. I am wondering whether the image is correctly saved to GridFS.

Any suggestion? Anyone else who experienced the same issue?

Thanks a lot!

  • The problem is due to the image upload. If I stream an image from a file located in the server, everything goes fine. I will try another way to make the upload. – rconfalonieri Oct 22 '14 at 10:53
  • 1
    I switched to stream the image over a Websocket (since my application had already one) by using `socket.io-stream` and it works now. The problem of uploading a file with **angular-file-upload** was that it was sending it as multipart. On the server side, when the file was received and streamed to the DB, its content contained some extra information that was preventing its correct readability (when streamed back to the client). – rconfalonieri Oct 29 '14 at 15:10

1 Answers1

0

Seems like your use of socket.io has solved your issue, but I just had to solve some of this for myself and thought I'd share.

For my case I need to be able to store information about the images such as image caption. I decided to stick with a standard html post form that contains all of input fields provided by the user as well as the file itself. I use Multer as middleware for the upload route, so by the time the request gets to my uploadImg route Multer has parsed it for the form fields as well as the file.

Multer stores the file as a temp file (haven't yet found a way to stream it to gridfs-stream while still parsing the form fields). A quick solution is to stream that saved temp file, and then delete it when it's done. Not an extremely scalable solution, but it's good enough for our case at the moment.

var uploadImg = function(req,res) {
      var writestream = gfs.createWriteStream({
        filename: req.files.file.name,
        mode:'w',
        content_type:req.files.file.mimetype,
        metadata:req.body,
      });
    fs.createReadStream(req.files.file.path).pipe(writestream);

    writestream.on('close', function (file) {
        res.send("Success!");
        fs.unlink(req.files.file.path, function (err) {
          if (err) console.error("Error: " + err);
          console.log('successfully deleted : '+ req.files.file.path );
        });
    });

};

Also see this: MongoError when uploading a file using mongoose, gridfs-stream and multer

Community
  • 1
  • 1
AddieD
  • 138
  • 7
  • Your approach is fine if you can store tmp files on the server. However, it won't be suitable if you use a cloud application platform for hosting your app, such as Heroku for instance (since it does not allow storing files temporarily). Thanks for sharing though ;) – rconfalonieri Dec 01 '14 at 10:10
  • Been successfully streaming directly to MongoDB on fileupload in production for a week or so now. Working great. For anyone interested see [this answer](http://stackoverflow.com/a/27163813/3304414) and [this pull request](https://github.com/willhuang85/skipper-gridfs/pull/9). – AddieD Dec 05 '14 at 04:47