How to Upload Files with Meteor.js?
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. :)
The Problem
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.
My Explanation
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.
My Solution
Installation
First, instead of using cfs:filesystem
, use cfs:gridfs
for your package.
meteor add cfs:standard-packages
meteor add cfs:gridfs
Syntax
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],
})
Permissions
Then, set up ‘deny’ and ‘allow’ rules based on your needs.
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
},
})
User Interface
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 })
}
})
})
},
})
Publication/Subscription
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.