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

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.



Solution 1:[1]

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

Solution 2:[2]

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);
}

Solution 3:[3]

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,
  },
};

Solution 4:[4]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 styfle
Solution 2 Pant
Solution 3 Reid
Solution 4 Hoang Nam