Simple image uploads in Meteor
While working on a simple online yearbook for my high school class of ‘16 in Meteor, I ran into the issue of uploading images to Meteor. Turns out, it’s not uncomplicated at all.
I messed around with CollectionFS, but unfortunately had the issue of images not loading and returning 503s, or taking a long while to load.
I decided to turn to the most popular and developer-friendly image host I know: Imgur.
I used the simple:imgur package. The upload()
function takes two arguments, options
and callback
.
The options require an apiKey
, the Imgur Client ID and image
, the base64-encoded image data string. The callback
function receives two arguments, the first being a Meteor.Error
object and the latter being an object containing the response from Imgur.
apiKey
: Registering an Imgur Application
The first step is to get an apiKey
by registering your application at Imgur’s OAuth 2 page. We need to choose ‘OAuth 2 authorization without a callback URL’, and once done, get the client ID (You’ll get an email about it, too).
image
: Converting file to base 64
In the submit
event of my upload form, I add a line to get the file that the user uploaded:
var file = $form.find('input[type="file"]')[0].files[0]
I also check if the file is an image or not:
if ( file && !file.type.match('image.*') ) return alert('Upload must be an image')
We’re now going to use the FileReader
API to get the base-64 representation of the image.
We’ll need to create a new reader and add a function to it’s onload
event, where we handle the image upload logic.
var reader = new FileReader()
reader.onload = function(e) {
// e.target.result holds the file's text
}
We’re going to convert the text into a Uint8Array
, convert it to a string, and then finally use btoa()
to convert the string to base-64 encoded ASCII string.
I directly do this in a convoluted one liner and a custom function inside an options
object I created to call Imgur.upload()
.
var options = {
apiKey: 'XXXXXXXXXXXXXXX',
image: btoa(uint8ToString(new Uint8Array(e.target.result)))
};
The uint8ToString
function is simple: it converts the Unicode values we get from the Uint8Array
representation of e.target.result
into ASCII strings, which can be converted to base-64 easily.
function uint8ToString(buffer) {
var length = buffer.length, str = ''
for ( var i = 0; i < length; i++ ) {
str += String.fromCharCode( buffer[i] )
}
return str
}
And with this, we’re able to create our options
object easily.
Uploading
I created a data
object to handle all data entered in the form by our user. In the Imgur.upload()
function, I add to it.
Imgur.upload(options, function(errMsg, imgurData) {
if ( errMsg ) return alert('File upload failed. Please upload an image of a smaller file size')
var imgData = {
link: imgurData.link,
deletehash: imgurData.deletehash
}
data.photo = imgData
})
}
I only store the two important parts of the response: the link
to the file and the deletehash
. I can easily show the file by using <img src="">
.
And we’re done!