[Solved] Receiving Multi-Part Form Data with Meteor + Multer?

I have express-oriented sample code for receiving multi-part form data from a provider:

var express = require('express')
var multer  = require('multer')
var app = express()

const callback = multer();

app.post('/callback', callback.single('json'), (req, res) => {
  res.status(200);
  res.send('API Event Received');

  const data = req.body.json;
  const events = JSON.parse(data);
  const eventType = events.event.event_type;
  const signatureRequestId = events.signature_request.signature_request_id;

  switch(eventType) {
    case 'signature_request_sent':
      console.log(`Signature request ${signatureRequestId} has been sent.`);
      break;
    case 'signature_request_viewed':
      console.log(`Signature request ${signatureRequestId} has been viewed.`);
      break;
    case 'signature_request_downloadable':
      console.log(`Signature request ${signatureRequestId} is downloadable.`);
      break;
    case 'signature_request_signed':
      console.log(`Signature request ${signatureRequestId} has been signed.`);
      break;
    case 'signature_request_declined':
      console.log(`Signature request ${signatureRequestId} has been declined.`);
      break;
    default:
      console.log('');
      break;
  }

});

On this SO post, I saw an example of connecting Meteor to Multer for purposes of receiving image files:

if (Meteor.isServer) {
  Meteor.startup(function () {
    multer({ dest: './uploads/',
        rename: function (fieldname, filename) {
            return filename+Date.now();
        },
        onFileUploadStart: function (file) {
            console.log(file.originalname + ' is starting ...');
        },
        onFileUploadComplete: function (file) {
            console.log(file.fieldname + ' uploaded to  ' + file.path);
            var fileName = file.name;
            var done=true;
        }
    })
  });
}

…but I haven’t figured out yet how to combine the two code samples.

What’s the correct way to use Multer in Meteor to receive multi-part form data?

Not sure why that SO answer was marked as correct (maybe the package has changed over time?).

Multer is a middleware sits between the http server, and user code.

In the example, the http server is provided by express, in your code, you want to use Meteor’s http server.
Meteor’s http server is provided by the WebApp package:

Looking at the docs for WebApp, the API is similar to express, and so we can mount multer on a single route like so:

import { WebApp } from 'meteor/webapp';
import multer  from 'multer';

const callback = multer({ dest: './uploads' });

// Mount the middleware first so it's run first
WebApp.connectHandlers.use('/upload', callback.single('json'));
// Then mount the user handler
WebApp.connectHandlers.use('/upload', function (req, res) {
  const file = req.file
  //do something with the file
});
1 Like

That worked. Thanks very much!

Note: in case it’s of interest, the provider is HelloSign. I’m having a great experience onboarding with them. Here’s my completed code.

//TELL HELLOSIGN YOUR CALLBACK URL
const hellosign = require('hellosign-sdk')({key: 'xxxxx'});
hellosign.account.update({
    callback_url: 'https://myDomain.com/myCallBackURL'
}).then((res) => {
    console.log('success setting hellosign account callback url');
}).catch((err) => {
    console.log('error setting hellosign account callback url');
    console.log(err)
});

//REST API FOR HELLOSIGN
const callback = multer({dest: './uploads'});

// Mount the middleware first so it's run first
WebApp.connectHandlers.use('/myCallBackURL', callback.single('json'));

// Then mount the user handler
WebApp.connectHandlers.use('/myCallBackURL', (req, res, next) => {
    res.setHeader('Content-Type', 'text/html');
    res.setHeader('X-Foo', 'bar');
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello API Event Received');

    const data = req.body.json;
    const events = JSON.parse(data);
    const eventType = events.event.event_type;
    const signatureRequestId = events.signature_request.signature_request_id;

    switch (eventType) {
        case 'signature_request_sent':
            console.log(`Signature request ${signatureRequestId} has been sent.`);
            break;
        case 'signature_request_viewed':
            console.log(`Signature request ${signatureRequestId} has been viewed.`);
            break;
        case 'signature_request_downloadable':
            console.log(`Signature request ${signatureRequestId} is downloadable.`);
            break;
        case 'signature_request_signed':
            console.log(`Signature request ${signatureRequestId} has been signed.`);
            break;
        case 'signature_request_declined':
            console.log(`Signature request ${signatureRequestId} has been declined.`);
            break;
        default:
            console.log('');
            break;
    }
});
1 Like