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

If you don’t want to use packages, you could just use the Amazon API for example.

Thank you all, but no external servers, and I want to host myself. Will hack something together, and post the results here.

Again, you can use that Meteor-Files package. It totally works for self hosting.

But let me tell you man, after MONTHS of pulling my hair out, I moved to Galaxy. Within the hour, I was up and running, it’s BEAUTIFUL. I pulled the plug on my Ubuntu server and haven’t looked back since. You have to be an apache/nginx PRO to get self hosting working, solid knowledge of DNS, port, and socket management is absoultely required.

Feel free to check out how wicked www.SkyRooms.IO is running now. I’m about to push an update with the Amazon File Storage working, but currently it’s using just /tmp hosting, which resets to an empty directory after an update goes live (which is why Galaxy tells you to get Amazon S3).

I wasted so much time. lol

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