POST multipart/form-data to Serverless Next.js API (running on Vercel / Now.sh)

Viewed 10360

I'm using a Vercel Serverless function to handle a file upload to Digital Ocean Spaces (identical API to AWS S3). However, I am running into issues handling multipart/form-data in the request handler.

On the front-end I'm using fetch to post a FormData() object with files and a couple of text fields. When logging the body and servers on the header, I can see everything there as expected, however when handling the multipart with Multer (I have also tried a couple of other similar packages) I am not able to retrieve any of the posted fields or files.

Also worth noting is that when using Postman to test the POST requests I'm running into the exact same issue, so I'm confident the issue lies in the serverless function.

Front-end:

const handleSubmit = async (values) => {
    const formData = new FormData();

    // build my Form Data from state.
    Object.keys(values).forEach(key => {
      formData.append(key, values[key]);
    });

    const response = await fetch("/api/post-submission", {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
      body: formData,
    });
    const json = await response.json();
  };

Serverless handler:

const util = require("util");
const multer = require("multer");

module.exports = async (req, res) => {
  await util.promisify(multer().any())(req, res);
  console.log("req.body", req.body); // >> req.body [Object: null prototype] {}
  console.log("req.files", req.files); // >> req.files []

  // Do the file upload to S3...

  res.status(200).json({ uploadData });
};

Expected behavior:

req.body and req.files should be populated with my submitted data.

5 Answers

2022 Update

You can use the multiparty package to parse multipart/form-data. The key is to also export a config object turning the bodyParser off. This will allow multiparty to work as designed and prevent the dreaded stream ended unexpectedly error.

The below code is a fully working example of an upload api page.

import { NextApiRequest, NextApiResponse } from "next";
import multiparty from "multiparty";

const uploadImage = async (req: NextApiRequest, res: NextApiResponse) => {
  const form = new multiparty.Form();
  const data = await new Promise((resolve, reject) => {
    form.parse(req, function (err, fields, files) {
      if (err) reject({ err });
      resolve({ fields, files });
    });
  });
  console.log(`data: `, JSON.stringify(data));

  res.status(200).json({ success: true });
};

export default uploadImage;
export const config = {
  api: {
    bodyParser: false,
  },
};

I'm not exactly sure about the Multer package, but there aren't any inherent limitations that prevent multipart/form-data from being handled by Serverless Functions on Vercel (AWS Lambda under the hood).

let multiparty = require('multiparty')
let http = require('http')
let util = require('util')

module.exports = (req, res) => {
    if (req.method === "POST") {
        let form = new multiparty.Form();
        form.parse(req, (err, fields, files) => {
            res.writeHead(200, { 'content-type': 'text/plain' });
            res.write('received upload: \n\n');
            res.end(util.inspect({ fields: fields, files: files }));
        });
        return;
    } else {
        res.writeHead(405, { 'content-type': 'text/plain' });
        res.end("Method not allowed. Send a POST request.");
        return;
    }
}

I created a demo repository with a deployed URL here

I was able to get multipart/form-data working by using busboy.

const Busboy = require('busboy');

module.exports = (req, res) => {
    const busboy = new Busboy({ headers: req.headers });

    busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
      console.log('File [' + fieldname + ']: filename: ' + filename);

      file.on('data', function(data) {
        console.log('File [' + fieldname + '] got ' + data.length + ' bytes');
      });

      file.on('end', function() {
        console.log('File [' + fieldname + '] Finished');
      });
    });

    busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
      console.log('Field [' + fieldname + ']: value: ' + val);
    });

    busboy.on('finish', function() {
      console.log('Done parsing form!');
      res.writeHead(303, { Connection: 'close', Location: '/' });
      res.end();
    });

    req.pipe(busboy);
}

i see you use form-data, you can set header content type into multipart/form-data;boundary=----WebKitFormBoundaryyrV7KO0BoCBuDbTL

Serverless function servers on the vercel service need a configuration file (vercel.json) with routes to be able to work, if you need to see how routes works : https://vercel.com/docs/configuration#project/routes

The config file will help you redirect your incoming post request to your js file. ex:

{
  "routes" : [
    {"src":"/api/post-submission", "methods": ["POST"], "dest": "<js file>"}
  ]
}
Related