Meteor server side file upload

Meteor server side file upload

In the last tutorial we have seen how to load the Medium Editor clone in our Meteor application. The editor come with the insert plugin that give us the possibility to add images and embedded in our text.

The plugin make use of the jquery file uploader and need an endpoint to call to stream the file to upload. Meteor from it side doesn’t support REST API so we have to implement one and make it able to accept data as the payload of the HTTP request.

This tutorial complete what we have seen before to have a fully function Medium Editor clone but the same can be used for all other times you need a REST endpoint for the file upload.

We will make use of the famous Meteor package CollectionFS to store the file. This plugin is very usefulll when you want to change the storage type. At the time of writing it support GridFS, Filesystem, Dropbox, S3 and some others. We will use the filesystem but if you want to use a different store you can refer to the instruction on the plugin GitHub page.

Start by adding the plugin to our project

meteor add cfs:standard-packages

meteor add cfs:filesystem

We will use also the Meteor Iron router. This plugin is one of the best routing plugin for Meteor but cannot accept the file payload in a POST request. To get the body of the request parsed and the posted file included as payload we can make use of a famous npm package called busboy. Meteor itself cannot use nom package so we need another plugin meteorhacks:npm.

meteor add meteorhacks:npm

meteor add iron:router

To load the npm module the plugin require a packages.json file in the root folder of the application where you specify the npm package to load.

 
{
  "busboy": "0.2.9"
}
 

Now when you start the application the package will be downloaded automatically.

What we want is to place a beforeAction on every route. If the request is a POST and it contains files we want to parse it and add the data to the request. We can use the onBeforeAction of the iron router. In the routing.js file put the code below :

 
if (Meteor.isServer) {
    var Busboy = Meteor.npmRequire('busboy')
 
    Router.onBeforeAction(function (req, res, next) {
        var files = []; // Store files in an array and then pass them to request.
        var image = {}; // crate an image object
 
        if (req.method === "POST") {
            var busboy = new Busboy({ headers: req.headers });
            busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
                image.mimeType = mimetype;
                image.encoding = encoding;
                image.filename = filename;
 
                // buffer the read chunks
                var buffers = [];
 
                file.on('data', function(data) {
                    buffers.push(data);
                });
                file.on('end', function() {
                    // concat the chunks
                    image.data = Buffer.concat(buffers);
                    // push the image object to the file array
                    files.push(image);
                });
            });
 
            busboy.on("field", function(fieldname, value) {
                req.body[fieldname] = value;
            });
 
            busboy.on("finish", function () {
                // Pass the file array together with the request
                req.files = files;
                next();
            });
            // Pass request to busboy
            req.pipe(busboy);
        }
        else{
            this.next();
        }
    });
}
 
 

This code make all the magics. It parse the file in the HTTP and place the files in the request.

Now that we have all the posted files in the request we can create the route to call with a POST Method on the server side of the Meteor application. Again in the routing.js file place the code below.

var Images = new FS.Collection("images", {
    stores: [new FS.Store.FileSystem("images", {path: "/uploads"})]
});
 
FS.HTTP.setBaseUrl('/files');
 
Router.route('/upload',function(){
 
        var files = this.request.files;
        var res = this.response;
        var newFile = new FS.File();
 
        newFile.attachData(files[0].data, {type: files[0].mimeType},function(err){
            newFile.name(files[0].filename);
 
            Images.insert(newFile, function (err, fileObj) {
               while(fileObj.url()==null);
                var resp = {
                    files: [{url: fileObj.url()}]
                };
 
                res.end(JSON.stringify(resp));
            });
        });
 
}, {where: 'server'});

Let’s explain the code. We defined a route /upload where we create a new FS.file that’s we save using the FSCollection plugin. For the needing of the Medium Editor we save just the first files that’s come in the request, but it’s easy to make it accept more then one just looping on the files variable.

Last thing to notice is how we created the response; The editors need a simple JSON response where the files URL is returned. In this case the FS.File url() method come in helps. We had to use a loop to wait the url become available and return the Json response as soon as it’s ready.

If you followed the Medium Editor tutorial below the necessary code to make the editor calling the right endpoint. You can replace the code exposed in the previous tutorial with this :

Template.edit_post.rendered= function(){
    editor = new MediumEditor('.editable', {});
 
    $(function () {
        $('.editable').mediumInsert({
            editor: editor,
            addons: {
                images: {
                    fileUploadOptions:{
                        url: 'upload',
                        acceptFileTypes: /(.|\/)(gif|jpe?g|png)$/i
                    }
                }
            }
        });
    });
};
 

We don’t need anything more. All the file download come from the CollectionFS plugin that’s expose all the route necessary to get and delete the uploaded files.

Conclusion

The information on this short tutorial completes the code needed to run a fully functional Medium Editor clone and explained how to use the power of a npm package inside the Meteor application. Also we have seen something not usual for a Meteor application but can be useful for example if you want to make a RESTFull API for your application, the server side routing.

Cheerss! Happy coding!

Adam Brown
Please follow and like us:

Leave a Comment