This is How I manage file upload inside Meteor without 3rd party services


#1

Hai guys, I don’t use third party services to manage files.

Then here is how I implement file upload inside Meteor, might be it’s usable for you.

The answes is simple, just define your server routes which is receive the files.
Get the files and save to Mongo. We can get raw Mongo object using Meteor/npm-mongo.
Then we use GridStore to manage the files. You can find the docs here : https://mongodb.github.io/node-mongodb-native/api-generated/gridstore.html

For server routes, you can use Picker or WebApp.
I choose to use WebApp.

Here is my server routes :

import {Meteor} from 'meteor/meteor';
import {WebApp} from 'meteor/webapp';
import formidable from 'formidable';
import fs from 'fs';
import {NpmModuleMongodb as MongoDb} from 'meteor/npm-mongo';
import path from 'path';

WebApp.connectHandlers.use('/', (req, res, next) => {
    const matchPath = matchPathFactory(req);

    if (matchPath('/server/upload')) {
        const form = new formidable.IncomingForm();

        form.parse(req, (err, fields, files) => {
            const fileId = App.getId();
            const fileName = fileId + path.extname(files.file.name);
            const db = Meteor.users.rawDatabase();
            const gs = new MongoDb.GridStore(db, fileId, fileName, 'w');

            gs.open((err, gs) => {
                App.handleError(err);

                gs.writeFile(files.file.path, (err, gridStore) => {
                    App.handleError(err);

                    gridStore.close((err, data) => {
                        if (err) {
                            throw err;
                        }

                        res.setHeader('Content-Type', 'application/json');
                        res.end(JSON.stringify({
                            id: data._id,
                            name: data.filename
                        }));
                    });
                });
            });
        });
    }
});

function matchPathFactory(req) {
    if (req.keys || req.segments || req.params) {
        throw 'keys or segments or params already defined';
    }
    return matchPath.bind(req);

    function matchPath(pattern) {
        if (this.url === undefined) {
            throw 'Please bind this to req';
        }

        this.keys = [];
        this.segments = pathToRegexp(pattern, this.keys).exec(this.url);
        this.params = {};
        if (this.segments && this.segments.length > 1) {
            this.keys.forEach((item, index) => {
                this.params[item.name] = this.segments[index + 1];
            });
        }
        return this.segments;
    }
}

And in client code, I just doing Ajax as usual.

uploadImage() {
    const file = $fileInput[0].files[0];
    const data = new FormData();
    data.append('file', file);
    $.ajax({
        url: '/server/upload',
        type: 'post',
        data: data,
        processData: false,
        contentType: false,
        success: () => {
            //do something
        },
        error: (err) => {
            App.handleError(err);
        }
    })
}

Optional.

If you want to delete the files, you can use raw Mongo object. Or make Meteor collection to fs.files.

// File: /imports/api/files/files.js

class FsFilesCollection extends Mongo.Collection {
    remove({id, ids}) {
        if (id) {
            FsChunks.remove({files_id: id});
            super.remove({_id: id});
        }
        if (ids) {
            FsChunks.remove({files_id: {$in: ids}});
            super.remove({_id: {$in: ids}});
        }

    }
}
export const FsFiles = new FsFilesCollection('fs.files');

And delete the files like this:

import async from 'async';
import {FsFiles} from '/imports/api/files/files';
import {Products} from '/imports/api/products/products';

//Inside meteor methods.
if (!this.isSimulation) {
    const product = Products.findOne(params.id);
    if (!product) {
       throw Meteor.Error('bad-request');
    }
    const MongoDb = require('meteor/npm-mongo').NpmModuleMongodb;
    const db = Meteor.users.rawDatabase();

    //It's WORK but don't effective
    // async.each(product.images, (item, cb) => {
    //     new MongoDb.GridStore(db, item.fileId, null, 'r').open((err, gs) => {
    //         if (err) {
    //             return cb(err);
    //         }
    //         gs.unlink((err, data) => {
    //             if (err) {
    //                 return cb(err);
    //             }
    //         });
    //     });
    // }, (err) => {
    //     if (err) {
    //         throw err;
    //     }
    // });

    if (product.images) {
        const fileIds = product.images.map((item) => item.fileId);
        FsFiles.remove({ids: fileIds});
    }
}

[ SOLVED ] Managing uploads for Meteor 1.3