Welcome to another episode of Continuous Improvement where we discuss ways to enhance your development skills and solve common coding challenges. I’m your host, Victor. Today, we’re going to talk about handling file uploads in a Meteor project.

Last week, a friend of mine ran into an issue when deploying his Meteor app with a file upload feature. It worked perfectly fine on his local machine, but once deployed, the server kept refreshing and failed to load any page. After investigating this issue, I found a solution that I want to share with you all.

The problem arises when using cfs:filesystem, which uploads the file to a public folder directory. This is not allowed by the server for security reasons, resulting in the page failing to load. However, we have a workaround to solve this issue by using GridFS as a storage adapter to insert files into MongoDB.

Let’s go through the steps to implement this solution in your Meteor project.

First, you need to install the necessary packages. Replace cfs:filesystem with cfs:gridfs in your project.

Open your terminal and enter the following commands:

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

Great! Once you’ve added the required packages, you can move on to the syntax changes. Instead of using FS.Collection, switch to FS.Store.GridFS when declaring your collection.

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

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

Now, let’s configure the permissions for your collection. Add the following deny and allow rules based on your requirements.

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

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

Moving on to the user interface. In your client template, add a file input button for users to select their file.

<input type="file" name="..." class="myFileInput">

Now, let’s handle the file upload event in your template.

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});
          }
        });
      });
   },
});

Don’t forget to set up the publication and subscription if you have removed the autopublish package.

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

Subscribe to it in your iron:router to ensure that you have the necessary data when rendering the template.

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

That’s it! By following these steps, you can successfully handle file uploads in your Meteor project, even when deployed to a server. If you’re using an Amazon S3 bucket, consider using cfs:s3 as the adapter. And as a last resort, consider Filepicker as an alternative approach for file uploads.

I hope this solution helps you overcome any file upload challenges you may encounter in your projects. Remember, continuous improvement is key to becoming a better developer.

Thank you for listening to this episode of Continuous Improvement. Stay tuned for more tips, tricks, and solutions to enhance your coding journey. I’m Victor, your host, signing off.