How to upload files with Meteor.js?

NOTE: This tutorial is using Meteor 1.1.3 with cfs:standard-pacage 0.0.2 (15th December 2014). Since this feature is under active development, it maybe further changed by the time you read this. Feel free to contact me if you have any questions;)

The Problem

Last week, a friend of mine asked me how to handle file upload with the Meteor project. I recommended him using Collection FS, so he followed the README.md instructions on their repo. The file upload feature works fine on localhost, but it doesn’t work once it is deployed to the free meteor testing server. In fact, the whole server would keep refreshing, not even load a page.

My Explanation

This is because FS.filesyetem upload the image to a public folder directory. It is not allowed by the server unless it is properly setup due to security concern. So we could use Grid FS as storage adaptor to insert images to MongoDB.

My Solution

Firstly, for installation, instead of using cfs:filesystem, package, use cfs:gridfs

meteor add cfs:standard-packages
meteor add cfs:gridfs

Secondly, for syntax, instead of using FS.Collection, change it to FS.Store.GridFS when you declare your collection:

var imageStore = new FS.Store.GridFS(“images”);

Images = new FS.Collection(“images”, {
 stores: [imageStore]
});

Thirdly, setup the deny and allow rules depending on your situation:

Images.deny({
 insert: function(){
 return false;
 },
 update: function(){
 return false;
 },
 remove: function(){
 return false;
 },
 download: function(){
 return false;
 }
 });

Images.allow({
 insert: function(){
 return true;
 },
 update: function(){
 return true;
 },
 remove: function(){
 return true;
 },
 download: function(){
 return true;
 }
});

Next, add a file input button in your client template for users to click.

<input type=”file” name=”…” class=”myFileInput”>

And handle the event as follow:

Template.Profile.events({
   ‘change .myFileInput’: function(event, template) {
      FS.Utility.eachFile(event, function(file) {
        Images.insert(file, function (err, fileObj) {
          if (err){
             // handle error
          } else {
             // handle success depending what you need to do
            var userId = Meteor.userId();
            var imagesURL = {
              “profile.image”: “/cfs/files/images/“ + fileObj._id
            };
            Meteor.users.update(userId, {$set: imagesURL});
          }
        });
     });
   },

Finally, don’t forget the publication/subscription in case you remove the autopublish package.

Meteor.publish(“images”, function(){ return Images.find(); });

And subscribe in your iron:router

Router.route(‘/profile’,{
 waitOn: function () {
 return Meteor.subscribe(‘images’)
 },
 action: function () {
 if (this.ready())
 this.render(‘Profile’);
 else
 this.render(‘Loading’);
 }
});

Hope it works for you. In case you are using Amazon S3 bucket, you may want to use cfs:s3 instead as the adaptor. And in the worst case, it all doesn't work for you, Filepicker would be an alternative approach to handle file upload for your reference: https://www.filepicker.io/

Originally published at victorleungtw.com on December 15, 2014.