Skip to content

Instantly share code, notes, and snippets.

@psi-4ward
Created December 13, 2016 19:40
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save psi-4ward/7d6b0d7e34ce92aa70bad65a377bc93c to your computer and use it in GitHub Desktop.
Save psi-4ward/7d6b0d7e34ce92aa70bad65a377bc93c to your computer and use it in GitHub Desktop.
Feathers.js multipart/file-data Upload Middleware

Feathers.js multipart/file-data Upload middleware

Parses multipart/form-data uploads and stream files to a blob-store.
After uploading the Services receives a json-body with the file references:

{
  "files": [
    {
      "key": "34525ca2-f774-444f-8e7e-7622bef69cb1",
      "filename": "13.jpg",
      "mimetype": "image/jpeg"
    },
    {
      "key": "7d757e42-23aa-4df3-8995-d4b8047d6a65",
      "filename": "06.jpg",
      "mimetype": "image/jpeg"
    }
  ],
  "createdAt": "2016-12-13T19:22:27.552Z",
  "_id": "Z3W69V7mcUPYfye3"
}

TODO

  • Add md5sum to meta?
  • Add length to meta?
  • How to check permissions
'use strict';
const path = require('path');
const NeDB = require('nedb');
const service = require('feathers-nedb');
const hooks = require('./hooks');
const uploader = require('./upload');
const store = require('fs-blob-store')(path.resolve(__dirname + '/../../../data/files'));
module.exports = function(){
const app = this;
const db = new NeDB({
filename: path.join(app.get('nedb'), 'files.db'),
autoload: true
});
db.persistence.setAutocompactionInterval(24 * 3600);
let options = {
Model: db,
paginate: {
default: 5,
max: 25
}
};
app.use('/files', uploader(store), service(options));
const filesService = app.service('/files');
filesService.before(hooks.before);
filesService.after(hooks.after);
};
'use strict';
const Busboy = require('busboy');
const uuidGen = require('uuid/v4');
const debug = require('debug')('upload');
module.exports = function(store) {
if(typeof store.createWriteStream !== 'function') {
// see https://github.com/maxogden/abstract-blob-store
throw new Error('Store has to be an abstract-blob-store compatible Instance')
}
return function upload(req, res, next) {
// Only for POST Requests of Type multipart/form-data
if(req.method !== 'POST' || !req.headers['content-type'].startsWith('multipart/form-data')) {
return next();
}
debug('Received multipart/form-data POST on '+ req.url);
req.body = { files: [] };
let uploading = 0, finished = false;
const busboy = new Busboy({headers: req.headers});
function done() {
// be sure busboy and every store-write is finished
if(uploading > 0 || !finished) return;
debug('Finished multipart/form-data POST on ' + req.url);
next();
}
// Listen for event when Busboy finds a file to stream.
busboy.on('file', function(fieldname, stream, filename, encoding, mimetype) {
debug('Uploading file ' + filename);
uploading++;
const meta = {
key: uuidGen(),
filename,
mimetype
};
const storeWS = store.createWriteStream(meta, function(err, data) {
uploading--;
if(err) return console.error(err);
debug('Uploaded ' + filename);
req.body.files.push(data);
done();
});
// Pipe the file into store
stream.pipe(storeWS);
});
// adopt non file fields to body
busboy.on('field', function(fieldname, val) {
req.body[fieldname] = val;
});
// Listen for event when Busboy is finished parsing the form
busboy.on('finish', function() {
finished = true;
done();
});
// Pipe the HTTP Request into Busboy
req.pipe(busboy);
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment