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);
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>"'
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;