
NOTE: This tutorial uses Meteor 1.1.3 with cfs:standard-package 0.0.2
as of December 15, 2014. Since this feature is under active development, it may have changed by the time you read this. Feel free to contact me if you have any questions. :)
Last week, a friend of mine asked me how to handle file uploads in a Meteor project. I recommended he use Collection FS, and he followed the README.md instructions on their repository. The file upload feature worked fine on localhost but failed once deployed to the free Meteor testing server. In fact, the server would keep refreshing and wouldn't even load a page.
This issue arises because FS.filesystem
uploads the image to a public folder directory. Due to security concerns, this is not allowed by the server unless properly configured. A workaround is to use GridFS as a storage adapter to insert images into MongoDB.
First, instead of using cfs:filesystem
, use cfs:gridfs
for your package.
meteor add cfs:standard-packages
meteor add cfs:gridfs
Next, when declaring your collection, switch from using FS.Collection
to FS.Store.GridFS
.
var imageStore = new FS.Store.GridFS("images")
Images = new FS.Collection("images", {
stores: [imageStore],
})
Then, set up 'deny' and 'allow' rules based on your needs.
Images.deny({
insert: function () {
return false
},
update:
created: function () {
return false
},
remove: function () {
return false
},
download: function () {
return false
},
})
Images.allow({
insert: function () {
return true
},
update:
created: function () {
return true
},
remove: function () {
return true
},
download: function () {
return true
},
})
Add a file input button in your client template for users to click.
<input type="file" name="..." class="myFileInput" />
Handle the event as follows:
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 on your needs
var userId = Meteor.userId()
var imagesURL = {
"profile.image": "/cfs/files/images/" + fileObj._id,
}
Meteor.users.update(userId, { $set: imagesURL })
}
})
})
},
})
Lastly, don't forget to set up publication and subscription in case you've removed the autopublish
package.
Meteor.publish("images", function () {
return Images.find()
})
Subscribe to it 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")
},
})
I hope this solution works for you. If you're using an Amazon S3 bucket, consider using cfs:s3
as the adapter. If all else fails, Filepicker could serve as an alternative approach for handling file uploads. For more information, visit Filepicker's website.