Search code examples
reactjsmongodbexpressmultermern

How to upload image file in MERN Stack Application with Multer


I'm trying to upload images as files using multer in my project, but I don't know why onCreate request in backend in the console I see on req.files: []; (empty array).

Here is where I create an element:

    const [imagesColors, setImagesColors] = useState([{image: [], color: ''}])
    
    const createProduct = (e) => {
        e.preventDefault ();
    
        const data = new FormData()
    
    data.append("name", name)
        data.append("description", description)
        data.append("processor", processor)
        data.append("ram", ram)
        data.append("storage", storage)
        data.append("imagesColors", imagesColors)
        data.append("price", price)
        data.append("type", type)
    
        console.log(data)
    
        fetch ('http://localhost:5000/products/create', {
          method: 'POST',
          body: data
        })...
    
    const handleInputChangeColor = (e, index) => {
        const { name, value } = e.target;
        const list = [...imagesColors];
        list[index][name] = value;
        setImagesColors(list);
      };
    
      const handleInputChangeImage = (e, index) => {
        const name = e.target.name;
        const file = e.target.files;
        const list = [...imagesColors];
        list[index][name] = file;
        setImagesColors(list);
      };
    
return (
...
    {imagesColors.map((x, i) => {
                        return (
                          <div className="box">
                            <label htmlFor="file" className="file--Input--Container">
                              <input
                                type="file"
                                id="file"
                                multiple
                                name="image"
                                className="file--Input"
                                filename="imageFile"
                                placeholder="Product Image"
                                onChange={e => handleInputChangeImage(e, i)}
                              />
                              <div className="file--Label--Container">
                                 <FaCloudUploadAlt className="upload--Icon"/> Upload Images
                              </div>
                            </label>
                            <select
                              onChange={e => handleInputChangeColor(e, i)}
                              value={x.color}
                              name="color"
                              defaultValue=""
                            >
                              <option selected value="">Color</option>
                              <option value='#4f5b66'>Space-gray</option>
                              <option value='#a7adba'>Silver</option>
                              <option value='#FFFFFF'>White</option>
                              <option value='#F63204'>Red</option>
                              <option value='#000000'>Black</option>
                              <option value='#0095CB'>Pacific-Blue</option>
                            </select>
                            <div className="btn-box">
                              {imagesColors.length !== 1 && <button onClick={() => handleRemoveClick(i)}>Remove</button>}
                              {imagesColors.length - 1 === i && <button onClick={handleAddClick}>Add</button>}
                            </div>
                          </div>
                        );
                      })}

When I console.log the state imagesColors it returns:

enter image description here

Here is the create function with multer function in backend:

const imageStorage = multer.diskStorage({
    destination: (req, file, callback) => {
        callback(null, "../../shpJS/frontend/src/styles/images")
    },
    filename: (req, file, callback) => {
        callback(null, file.name)
    }
})

const uploads = multer({storage: imageStorage})

router.post('/create', uploads.array("imagesColors"), async (req, res) => {
    console.log(req, ' asd')
    try {
        const data = {
            name: req.body.name,
            description: req.body.description,
            processor: req.body.processor,
            ram: req.body.ram,
            storage: req.body.storage,
            imagesColors: req.files,
            price: req.body.price,
            type: req.body.type,
            likes: req.body.likes
        }
        let product = await productService.create(data)
        res.status(201).json(product)
    } catch (error) {
        res.status(500).json({error: error})
    }
})

And the result in console from request:

enter image description here


Solution

  • To send multiple files in the frontend using FormData API, you have to append those files one by one. You can append them to the same field, and that field will arrive as an array in the backend. If you want to send additional data along with each file, append that data to a different field in the same order.

    In your case, it looks like this:

    for (const imageAndColor of imagesColors) {
      data.append('images', imageAndColor.image);
      data.append('colors', imageAndColor.color);
    }
    

    And in the backend change uploads.array("imagesColors") to uploads.array("images"). Your images will be in req.files and the colors in req.body.colors. The orders of both arrays are guaranteed — the first image is the first element in both arrays, second image is second element and so on.