Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor: Uploading an image and saving to the database as base64 string

I have a Meteor app and I am interested in getting image upload to work in the simplest possible manner.

The simplest manner I can come up with is to somehow convert the image to a base64 string on the client and the save it to the database as a string.

How is it possible to convert an image on the users filesystem to a base64 string and then save it to the database?

like image 347
Nearpoint Avatar asked Oct 19 '14 20:10

Nearpoint


2 Answers

You can use an HTML5 file input :

HTML

<template name="fileUpload">
  <form>
    <input type="file" name="imageFile">
    <button type="submit" disabled={{submitDisabled}}>
      Submit
    </button>
  </form>
</template>

Then listen to the change event and use a FileReader to read the local file as a base64 data url that we're going to store in a reactive var :

Template.fileUpload.created=function(){
  this.dataUrl=new ReactiveVar();
};

Template.fileUpload.events({
  "change input[type='file']":function(event,template){
    var files=event.target.files;
    if(files.length===0){
      return;
    }
    var file=files[0];
    //
    var fileReader=new FileReader();
    fileReader.onload=function(event){
      var dataUrl=event.target.result;
      template.dataUrl.set(dataUrl);
    });
    fileReader.readAsDataURL(file);
  }
});

Then we can use the reactive var value to allow/disallow form submission and send the value to the server :

Template.fileUpload.helpers({
  submitDisabled:function(){
    return Template.instance().dataUrl.get();
  }
});

Template.fileUpload.events({
  "submit":function(event,template){
    event.preventDefault();
    //
    Meteor.call("uploadImage",template.dataUrl.get());
  }
});

You will need to define a server method that saves the dataUrl to some collection field value, what's cool about dataUrls is that you can use them directly as an image tag src.

Note that this solution is highly unscalable as the image data won't be cachable and will pollute the app database regular communications (which should only contain text-like values).

You could fetch the base64 data from the dataUrl and upload it to Google Cloud Storage or Amazon S3 and serve the files behind a CDN.

You could also use services that do all of this stuff for you like uploadcare or filepicker.

EDIT :

This solution is easy to implement but comes with the main drawback that fetching large base64 strings from mongodb will slow your app from fetching other data, DDP communications are always live and not cachable at the moment so your app will always redownload image data from the server.

You wouldn't save dataUrls to Amazon, you would save the image directly, and it would be fetched by your app using an Amazon URL with a cachable HTTP request.

You have two choices when it comes to file upload : you can upload them directly from the client using specific javascript browser APIs or you can upload them within Node.js (NPM modules) APIs in the server.

In the case you want to upload from the server (which is usually simpler because you don't need to require that the users of your apps authenticate against third party services, only your server will act as a trusted client to communicate with Amazon API), then you can send the data that a user want to upload through a method call with a dataUrl as argument.

If you don't want to dive into all this stuff consider using uploadcare or filepicker, but keep in mind that these are paid services (as is Amazon S3 BTW).

like image 155
saimeunt Avatar answered Nov 09 '22 21:11

saimeunt


Not sure if this is the best way, but you can easily do this with a file reader. In the Template event handler where you get the file contents, you can pass the file to the reader and get back a base64 string. For example, something like this:

Template.example.events({
  'submit form': function (event, template) {
    var reader = new FileReader();
    var file = template.find('#fileUpload').files[0]; // get the file

    reader.onload = function (event) {
      // event.target.result is the base64 string
    }
    reader.readAsDataURL(file);
  }
});
like image 2
mark Avatar answered Nov 09 '22 19:11

mark