Search code examples
node.jstypescriptexpressgoogle-cloud-functionsmulter

req.body is [Object: null prototype] {} and req.files is empty when using multer to handle POST request containing form data


I'm trying to send form data using Postman to an express server hosted on Firebase functions. This is the code I'm using to receive the POST request:

import * as functions from 'firebase-functions';
import express = require('express');
import multer = require('multer');
import * as bodyParser from 'body-parser';

const app = express();
const upload = multer().array("attachment");

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.post('/upload', upload, (req, res) => {
    console.log(req.body);
    console.log(req.files);
    res.sendStatus(200);
});

export const server = functions.https.onRequest(app);

Here's the postman setup enter image description here

When I send the post request the req.body and req.files are logged as the following:

i  functions: Beginning execution of "server"
>  [Object: null prototype] {}
>  []
i  functions: Finished "server" in ~1s

I feel like it should be dead simple... What am I doing wrong?

EDIT:

As requested here is the request header

{
    'postman-token': '816a8871-bb12-470f-93f8-13cb6024815d',
    host: 'localhost:5001',
    'content-type': 'multipart/form-data; boundary=--------------------------115218701862318827186009',
    'content-length': '316657',
    connection: 'close'
}

and here is the curl code

curl --location --request POST 'http://localhost:5001/hosting-name/us-central1/server/upload' --form 'testKey="testValue"' --form 'attachment=@ <path_to_your_file>"'

Solution

  • I figured it out thanks to this blog post: https://mikesukmanowsky.com/firebase-file-and-image-uploads/

    Firebase and Multer DO NOT work together. I adapted the code in the blog post to work in my Typescript project here:

    index.ts

    import * as functions from 'firebase-functions';
    import express = require('express');
    import * as bodyParser from 'body-parser';
    import formMiddlware from "./formMiddleware";
    
    const app = express();
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: true}));
    
    app.post('/upload', formMiddlware, (req: any, res: any) => {
    
        console.log(req.body);
        console.log(req.files);
    
        res.sendStatus(200);
    
    
    });
    
    export const server = functions.https.onRequest(app);
    

    formMiddleware.ts

    import * as Busboy from "busboy";
    import os from "os";
    import fs from "fs";
    import path from "path";
    
    type Dict = {
        [key: string]: any
    }
    
    function formMiddlware (req: any , res: any, next: any) {
        // See https://cloud.google.com/functions/docs/writing/http#multipart_data
        const busboy = new Busboy.default({
          headers: req.headers,
          limits: {
            // Cloud functions impose this restriction anyway
            fileSize: 10 * 1024 * 1024,
          }
        });
      
        const fields: Dict = {};
        const files: any[] = [];
        const fileWrites: any[] = [];
        // Note: os.tmpdir() points to an in-memory file system on GCF
        // Thus, any files in it must fit in the instance's memory.
        const tmpdir = os.tmpdir();
      
        busboy.on('field', (key: string, value: any) => {
          // You could do additional deserialization logic here, values will just be
          // strings
          fields[key] = value;
        });
      
        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
          const filepath = path.join(tmpdir, filename);
          console.log(`Handling file upload field ${fieldname}: ${filename} (${filepath})`);
          const writeStream = fs.createWriteStream(filepath);
          file.pipe(writeStream);
      
          fileWrites.push(new Promise((resolve, reject) => {
            file.on('end', () => writeStream.end());
            writeStream.on('finish', () => {
              fs.readFile(filepath, (err, buffer) => {
                const size = Buffer.byteLength(buffer);
                console.log(`${filename} is ${size} bytes`);
                if (err) {
                  return reject(err);
                }
      
                files.push({
                  fieldname,
                  originalname: filename,
                  encoding,
                  mimetype,
                  buffer,
                  size,
                });
      
                try {
                  fs.unlinkSync(filepath);
                } catch (error) {
                  return reject(error);
                }
      
                resolve();
              });
            });
            writeStream.on('error', reject);
          }));
        });
      
        busboy.on('finish', () => {
          Promise.all(fileWrites)
            .then(() => {
              req.body = fields;
              req.files = files;
              next();
            })
            .catch(next);
        });
      
        busboy.end(req.rawBody);
    }
    export default formMiddlware;