Recommended way to upload images in meteor: No plugins, no external services (Solved)

I’m currently working on cloud9, I try not to use modules unless I have no choice (bad experiences coming from PHP:WordPress)

I checked out SkyRooms, and sleek is the word that comes to mind, very clean UI.

Thank you! I’m actually a full time PHP and Wordpress developer. I have to say, make sure you follow the post / postmeta database model in to your collections, WAY easier to work with in this schema.

I hear you about modules. I built SkyRooms on Wordpress with open source plugins and had to trash it, was so slow even with CDN and caching. Total garbage.

Hello,

Finally got something working:

//server.js

//check if user is actually user
Meteor.methods({
    checkuploaduser: function (userId) {
        if (userId !=null && this.userId == userId) {
            valid = true;
            picid = userId
        }
    }
});

//upload stuff
WebApp.connectHandlers.use('/file', (req, res) => {
if (valid) {
res.setHeader("Access-Control-Allow-Methods", "PUT");
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === 'OPTIONS') {
    res.writeHead(200);
    res.end();
    return;
    } else if (req.method === 'PUT') {
    if (!req.headers['content-type'].startsWith('image')) {
        res.writeHead(400);
        res.end();
    }
    let getFile = Meteor.wrapAsync(done => {
        let chunks = [];
        var chunk = null;
        while (null !== (chunk = req.read())) chunks.push(chunk);
        // req.on('readable', () => {
        //     chunks.push(req.read());
        // });
        //console.log(chunks)
        
        req.on('end', () => {
            done(undefined, Buffer.concat(chunks));
        });
    });
    let buffer = getFile();
    //console.log(buffer)
	//encode buffer as base64
    var base64data = new Buffer(buffer).toString('base64');
    //console.log(picid);
	//base64 as image in mongo
    Meteor.users.update({_id: picid}, {$set: {"profile.image": base64data}});
	//the comments below are for saving to local dir if you want
		//let path = process.env['METEOR_SHELL_DIR'] + '/../../../public';
		// const fs0 = require('fs');
		// filename = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
		// fs0.writeFile(path + "/" + filename, buffer, Meteor.bindEnvironment(function (err) {
			// if (err) {
				// console.log("Error:" + err);
			// } else {
				// console.log("Success");
			// }
		// }));
    res.writeHead(200);
    res.end();
} }
});
//client.js
Template.changeimage.events({
  'change input[type="file"]': function (event) {
    var file = event.target.files[0]
    console.log(file)
    var xhr = new XMLHttpRequest()
    userId=Meteor.userId()
    xhr.open('PUT', 'https://yourapp.com/file', true)
       xhr.onload = (event) => {
          console.log('done uploading!');
        };
    xhr.upload.onprogress = (event) => {
      let percent = 100 * (event.loaded / event.total);
      console.log(percent+'% uploaded');
    };
    Meteor.call('checkuploaduser', Meteor.userId());
    xhr.send(file);
  }
})
//template html
<template name="changeimage">
<input type="file">
</template>

Got a lot of help from this link:
https://iamlawrence.me/uploading-files-with-meteor

Its a bit buggy, I’m sure someone more experienced can make it better, but does the job for me.

3 Likes

Oh hey very cool!

So your code is pushing an uploaded file out to a receiving server for storage? Very cool.

I’m having an issue with image compression / optimization on S3 and CloudFront.

I do not agree, at least not with with Meteor and an EC2 instance (Ubuntu) and I don’t think it applies to any other Meteor + cloud hosting solution either. :smile:

2 Likes

I am definitely not that kind of expert, so yes.

I think it would be better to create a new question, and send me the link.

1 Like

Even tho I’m a bit late to the party I thought I’d give you a few quick suggestions,

  1. Check so that the upload is smaller than 16MB, MongoDB won’t like you otherwise :slight_smile:

  2. Try not to use the profile field, it might be tempting but it is not advised (instead of listing the reasons I’ll point ya’ to this part of the guide: https://guide.meteor.com/accounts.html#dont-use-profile )

  3. Maybe try something like minio, it is at least self-hosted. The nice thing about it is that you can generate a “pre-signed” URL to which the client can upload a file. It is also a lot more expensive and slower to host blobs on a database, it isn’t made for that specific application whilst the filesystem is perfect for the job! You can also easily switch to S3 or any other S3 compatible hosting if you so wish!
    I’d suggest to just upload the profile image (in this case) with the same ID as the user, then you can easily just get it from the bucket at any time, you could also do cool stuff such as adding several images of different sizes by just appending the size to the end of the filename and gain some performance.

(tl;dr, minio is like S3 (and compatible with it), self-hosted and is an extremely active project, use it to upload files to a proper fast filesystem!)

1 Like

Hello Novium,

Thank you for the suggestion. I started writing python handle the uploads to run on server, move outside app directory to prevent refreshes, and return a link. I’ll look into minio.

In anycase, I am done with the project, and on to something more serious. I decided to create it because I wanted to learn Meteor. This will come in handy for important projects.

Reviving an old thread here, but hoping someone can help.

In the code above, @crane3700 (who I suspect is no longer around here) uses a Method to validate the user by setting ‘valid’ to ‘true’ inside the Method if the user is logged in.

I don’t understand how this ‘valid’ variable can be used outside of the method, and especially how it can be used for the specific user who’s logged in.

When I try to do this I just get ‘valid’ is undefined. Or if I set it to false in a high scope it just stays as false.

Is there a way to do this, or is this a dead-end?

Hello gazhayes,

This user is still here.

I don’t remember what of much I did with that code, but it looks like I
passed the userId when calling the function, or I had defined it in an
external file which auto appends.

Can you provide more information as to what you want to do, so I can
provide a clear answer?

1 Like

Is it still running?

Check so that the upload is smaller than 16MB, MongoDB won’t like you otherwise :slight_smile:

  1. GridFS can easily solve this.

Oh wow! I thought you had disappeared! :grinning:

I’m basically in the exact same situation you were in - I want to be able to upload images to the server but want to do it in the most simple possible way.

I do need to validate the userId and and also get some other data from the client too.

You call this:

Meteor.methods({
    checkuploaduser: function (userId) {
        if (userId !=null && this.userId == userId) {
            valid = true;
            picid = userId
        }
    }
});

And then use the variable here:

WebApp.connectHandlers.use('/file', (req, res) => {
if (valid) {

But I don’t know how you are getting it to work, I can’t use valid outside of the Method. Do you remember if that code actually worked for you, or did you end up doing something else?

1 Like

I remember it working, I might have called another dependency. I’ll set up a very basic vanilla meteor upload, and send you the code today.

That would be extremely cool!

//start client.js
Template.upload.events({
	//call upload function when the file-input element changes
    'change #file-input': function(event){
        upload_function(event);
    },
});

//upload funtion
upload_function = function (event) {
	var file = event.target.files[0];
	var reader = new FileReader();
	reader.onload = function(fileLoadEvent) {
	//call created upload method and pass file name, and file-reader info
	Meteor.call('upload', file.name, reader.result,function(error, result) {
        console.log(result)
		});
	};
  reader.readAsBinaryString(file);
}
//end client.js


//start server.js
import fs from 'fs';
Meteor.methods({
'upload': function(fileinfo, filedata) {  
	//get user info based on current logged in user
    var user_info = Meteor.users.findOne({"_id": this.userId}, {fields: {"_id": 1}}); 
	 if(!user_info){
        return "Nope, not happening";
    }
	//path can be any directory you like, I decided to upload to public
	/*if you want to create directories on the fly,
         you'll need to add some extra code, its really easy.*/
    var path = process.env['METEOR_SHELL_DIR'] + '/../../../public/';
	 
	//add user id to file name and move 
    fs.writeFile(path+user_info._id+fileinfo, filedata, {encoding: 'binary'});
 },
});
//end server.js

//template stuff
<head>
  <title>upload</title>
</head>

<body>
  {{> loginButtons}}
  {{> upload}}
  
</body>


<template name="upload">
  <input id="file-input" class="form-control" type="file">
</template>

I was passing the UserID in the functioncall initially, which is really insecure, that’s why the old bit was not working (I just found the old code).

Now we just check on the server to find the current user, without any client input.

You can add other code to check !user or what not, let me know if you need more help.

The page will reload for everyone because a dir changed, so you can move it to some external dir/service instead (like cloudinary)

1 Like

No sir, I shut down a while ago. Cheers,

Thanks for much for that! It looks like that’s exactly what I need. I’ll post again if I need help but I “get it” now :smiley:

I think this code enables anyone to upload files … after the first authenticated user is enabled … in the file upload session there is nothing to identify who is uploading with the PUT method