1

I am using axios to send a FormData object, containing a categories field, an array of strings, to an express server. I am using multer to parse the FormData recieved, however req.body.categories is a single string after parsing eg 'cat1, cat2, cat3', not an array of strings eg ['cat1', 'cat2', 'cat3']

This is the code i am using to send the FormData:

newProduct: {categories: string[]}

const formData = new FormData()

for (let key of Object.keys(newProduct)){
  formData.append(key, newProduct[key])
}

const response = await axios.post('/api/products', formData)

I have tried to JSON.stringify(categories) before appending this to the formdata but multer understandably just parsed this as a string aswell.

It feels 'icky' to just use categories.split('') after multer has parsed the rest of the formdata so graciously for me, but is there another way or am i doing something wrong?

1

2 Answers 2

3

To send arrays, you need to append each value individually using the same key.

const formData = new FormData()

for (const [key, value] of Object.entries(newProduct)){
  if (Array.isArray(value)) {
    for (const arrayItem of value) {
      formData.append(key, arrayItem);
    }
  } else {
    formData.append(key, value);
  }
}

On the server, request.body.categories will either be a string, if there is only one category, undefined if there are zero categories, or an array of strings, if there are multiple categories. It is easiest to check if it is an array, and if not, to turn it into one.

app.post('/api/products', multer().none(), (request, response) => {
  const {body} = request;
  const categories = Array.isArray(body.categories)
      // >= 2 items
    ? body.categories
    : body.categories === undefined
        // 0 items
      ? []
        // 1 item
      : [body.categories];
});

Unfortunately, there is no array type with multipart/form-data. It will only be an array if a key has multiple values. That is why you need code to turn it into an array when there aren't multiple values. Furthermore, you need to handle the case where there are zero categories. With multer, you can check with request.body.categories === undefined and handle such cases as empty arrays.

If you're curious to see this in action, I have created a minimal Express app to test my answer. (You can't run this snippet, but only snippets can be hidden)

import express from 'express';
import multer from 'multer';

const app = express();

app.get('/', (_request, response) => {
  response.send(`<!doctype html>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.8.4/axios.min.js" integrity="sha512-2A1+/TAny5loNGk3RBbk11FwoKXYOMfAK6R7r4CpQH7Luz4pezqEGcfphoNzB7SM4dixUoJsKkBsB6kg+dNE2g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <script type="module">
        async function sendProduct(product) {
            const formData = new FormData()

            for (let [key, value] of Object.entries(product)){
                if (Array.isArray(value)) {
                    for (const item of value) {
                        formData.append(key, item);
                    }
                } else {
                    formData.append(key, value);
                }
            }

            const response = await axios.post('/api/products', formData);
        }

        await sendProduct({categories: []});
        await sendProduct({categories: ['abc']});
        await sendProduct({categories: ['abc', 'def']});
    </script>
`)
});

app.post('/api/products', multer().none(), (request, response) => {
  const {body} = request;
  console.log(body);
  const categories = Array.isArray(body.categories)
      // >= 2 items
    ? body.categories
    : body.categories === undefined
        // 0 items
      ? []
        // 1 item
      : [body.categories];
  console.log(categories);
  response.send(body);
});

app.listen(3031, () => {
  console.log('listening on http://localhost:3031');
});

-2

I would just use the built-in WHATWG Request.formData() available in node, deno, or bun together with JSON.parse() on the server and with JSON.stringify(), as you tried, on the client. Here's an example you can run in the browser, which can be used on the server:

(async () => {
  let fd = new FormData();
  fd.append("arr", JSON.stringify(['cat1', 'cat2', 'cat3']));
  await new Response(fd).text()
    .then((body) => {
      console.log(body);
      const boundary = body.slice(2, body.indexOf("\r\n"));
      return new Response(body, {
          headers: {
            "Content-Type": `multipart/form-data; boundary=${boundary}`,
          },
        })
        // What you do in the server
        .formData()
        .then((data) => {
          // Here's your Array 
          console.log(JSON.parse(data.get("arr")));
          return data;
        }).catch((e) => {
          throw e;
        });
    }).catch(console.warn);
})().catch(console.warn);

1
  • Just my personal opinion: It's better to use multer, because you can set limits on all sorts of things, e.g. file size, so you don't get overrun with large files. It also lets you specify what fields are allowed to be a file, if any, which makes input validation easier.
    – lusc
    Commented Mar 28 at 17:35

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.