0

I in my React app I send a request containing a JSON body with various fields, among which is an array of File type images.

const body = {
  title: 'title',
  price: 100,
  images: [...]
};

axios.post(url, body);

This doesn't work and the images array arrives at the Express app empty. I have consulted the internet and have tried using FormData. However, all the solutions I saw (example) only have images in them, i.e.

const images = [...];
const data = new FormData();
images.forEach(img => data.append(img.title, img));

But I want the entire thing - the text and the images, because this input is supposed to go into MongoDB. I have tried simply putting the entire object in a FormData:

const data = new FormData();
data.append('body', body);

But I must not understand how FormData actually works because trying to iterate over the req.body over in Express just yields nothing:

app.get((req, res) => {
  const data = Object.fromEntries(req.body) // yields nothing
}

Any other attempts like using multer reinforce that I'm doing something wrong, because I cannot for the life of me get a non-empty request body.

What I want to do is to be able to send a full HTTP body of data that includes text, numbers, and an array of images, such that over on the Express app I could save those images to disk. Any nice suggestions would be appreciated.

Ronny Efronny
  • 1,148
  • 9
  • 28
  • Just to give an idea (it may not be the best) I recently had a similar issue (I was using a nodejs azure function to upload the images and didn't want it to get too complex) and I solved it by just passing the raw base64 as a string to the server like this `images: ['base64string','base64string']` – Shmili Breuer May 13 '21 at 14:20
  • @ShmiliBreuer an the rest of the JSON would just be JSON? Or translate the entire thing into base64? – Ronny Efronny May 13 '21 at 14:27
  • The rest is regular JSON (the images are also plain json, no FormData involved), I can't say if this is a proper/efficient way to do this it was just the easiest most cost effective way for me on that particular low priority use case. – Shmili Breuer May 13 '21 at 14:30
  • I might be missing something. You turned the files into strings with `Buffer.from(img).toString('base64')` I assume, how did you turn them back on the server into viable files? – Ronny Efronny May 13 '21 at 14:44
  • In my case I was uploading it on my express server to an image hosting service called imagekit and they accept a base64 string to upload an image (so I didn't need to convert it to a file), but in your case to save it to the disk maybe this https://stackoverflow.com/a/6933413/3161811 can help. – Shmili Breuer May 13 '21 at 15:04

1 Answers1

1

You cannot use json to send images(not the original file), but there's an option with form data. First, you have to separate the files from the normal data, and parse the normal data to a string.

const images = [...];
const body = new FormData();
// you have to put the images in the same field, and the server will receive an array
images.forEach(img => body.append('images', img));
// the other data to send
const data = {
  someData: true,
  moreData: 1,
arrOfNormalData: [23,10];
}
const parsedData = JSON.stringify(data);
body.append('_jsonData', parsedData);
axios.post(url, body);

You are sending the json data as string in the _jsonData field of your request. Then en your node server, you have to install multer on configure it ej

const path = require('path');
//this is for a random id for file name
const { nanoid } = require('nanoid');
const multer = require('multer');

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads')
  },
  filename: function (req, file, cb) {
    cb(null, nanoid() + path.extname(file.originalname));
  }
})
 
const upload = multer({ storage: storage });

Then you have can create a middleware to parse the data (remember we converted it to a string) to a object.

const parseJson = (field = '_jsonData') => {
    return (req, res, next) => {
        if (req.body[field] && typeof req.body[field] === 'string') {
            req.body = JSON.parse(req.body[field]);
        }
        next();
    };
};

Then lets use in in our route

app.get(upload.array('images',10),parseJson(),(req, res) => {
  const data = req.body;// here the json data
const files = req.files;// here the uploaded files
}

We have added the upload.array('images',10) where images is the field of files and 10 is the max number of files We added our parseJons middleware to parse the string to json Don't use base64, you can have issues if you are working with base64. Use formdata

Denes
  • 106
  • 1
  • 1
  • 5
  • I've attempted implementing your method, but my server crashes somwhere between `upload.array()` and `parseJson()`. I tracked the request with logs and it reaches the storage functionality, but does not reach the parsing, and no files are being written to disk in practice, so I am very confused about what could be happening there. Do you have any idea about this? – Ronny Efronny May 13 '21 at 15:45
  • Plese can you share the logs an errors, and try `const upload = multer({dest: 'uploads/'})`, and try with `post` method instead `get` – Denes May 13 '21 at 16:19
  • I did try with `post`, that's irrelevant though. The errors are related to Jade, which I don't know: `express:view require "jade"`, `express:view lookup "error.jade"` then `stat` and `render` about `error.jade`. Prior to this I see routing to some anonymous functions, i assume those are the middleware. Is the `dest: 'uploads/'` in addition to `storage` or instead? – Ronny Efronny May 13 '21 at 17:46
  • @RonnyEfronny use `const upload = multer({dest: 'uploads/'})` instead the `storage`, if you see "jade" maybe is a problem with your views, pls verfy your express setup with your views – Denes May 13 '21 at 17:51