How to use Multer middleware to upload array of images
Asked Answered
C

3

5

Im trying to use Multer to upload an array of images. At the client side i have a FormData called pictures.

pictures array, from react-native-image-picker:

const [pictures, setPictures] = useState([]);
const imagePickerCallBack = data => {
    const picturesData = [...pictures];
    const index = picturesData.length;
    const image = {
      image: data.uri,
      fileName: data.fileName,
      type: data.type,
      index: index,
    };

    picturesData.push(image);

    setPictures(picturesData);
    setLoad(false);
  };

Step 1 - Create formData with all images:

const data = new FormData();

pictures.forEach(pic => {
        data.append('pictures', {
          fileName: pic.fileName,
          uri: pic.image,
          type: pic.type,
        });
      });
const headers = {
    'Content-Type': 'multipart/form-data',
    'x-access-token': token,
  };

const diaryUpdatePost = await post(`diary/uploadPictures/${diary}`, body, {
    headers,
  });

Step 2 - Get the request at server side. Im setting up multer and routers:

const router = express.Router();
const multer = require('multer');

const storage = multer.diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads/');
  },
  filename(req, file, cb) {
    cb(null, `${file.fieldname}-${Date.now()}`);
  },
});

const upload = multer({ storage, limits: { fieldSize: 25 * 1024 * 1024 } });

// Multer with the same FormData (client)
router.post('/uploadPictures/:name', upload.array('pictures'), diaryController.uploadDiaryPictures);

And finally my diaryController, where i need to get all files:

exports.uploadDiaryPictures = async (req, res) => {
  // Logging []. I cant access files from here
  console.log(`files ${req.files}...`);
};

I already tried to use express-fileupload, but req.files return undefined. Some ideia to help? Thx.

Chunk answered 13/5, 2021 at 17:38 Comment(2)
shouldn't you be sending data instead of body in Axios post request?Costermonger
I'm just contextualizing the execution, but the post function is in another class, so it uses the body, which is a parameter.Dayna
C
11

You need to give a count of files you expect to upload:

upload.array('pictures', <number_of_pictures>)

Or if it is allowed to be any number:

upload.any('pictures')

You should also add the file itself to your form data

data.append('pictures', {
    name: pic.fileName,
    file: pic.image,
    type: pic.type,
});
Costermonger answered 13/5, 2021 at 18:0 Comment(10)
Hey bro, first of all, thx for helping. One question, i need to set file property with file uri?Dayna
@Flávio Costa, the file should be just the file. If you iterate over files array then file: picCostermonger
Ok, i set multer middleware with upload.any('pictures'), and added file property, with pic value. But im still getting req.files.length = 0.Dayna
It could be because im not using the Multer config from App.js file?Dayna
No config of multer is required for frontend requestCostermonger
I realized that the request takes longer to reach the server when the formData uri parameter is filled. When I remove this parameter, the request happens very fast, as if there was no uploadDayna
can you add the code for how you created the pictures array?Costermonger
I am out of options now. Your setup seems fine. As a final attempt can you try file: pic.image, name: pic.fileName settingCostermonger
It didn't work; / I'll keep trying, when I get it resolved I comment hereDayna
I got it! You are right, changed fileName -> name and worked. Thx my friend.Dayna
E
2

None of the answers here helped. The solution for me was to iteratively append EACH file object from the files array to the same field name given in Multer, instead of appending the files array itself to the field name given in Multer.


So from this:

export const addFiles= createAsyncThunk(
  "addFiles",
  async (payload: any, thunkApi) => {
    const formData = new FormData();
    // Here was the problem -- I was appending the array itself
    // to the "files" field
    formData.append("files", payload.files);
    formData.append("data", JSON.stringify(payload?.data || {}));

    const response = await axios.post('/user/products/files', formData);
    
    if(response){
      return response;
    }
    return thunkApi.rejectWithValue("");
  }
);

I did this:

export const addFiles= createAsyncThunk(
  "addFiles",
  async (payload: any, thunkApi) => {
    const formData = new FormData();
    // The following loop was the solution
    for (const file of payload.files) {
      formData.append("files", file);
    }
    formData.append("data", JSON.stringify(payload?.data || {}));

    const response = await axios.post('/user/products/files', formData);
    
    if(response){
      return response;
    }
    return thunkApi.rejectWithValue("");
  }
);

This was my Multer configuration:
multer({ dest: "/uploads" }).array("files")

The files posted to my endpoint were then available to me at:
req.files

PS: Although the accepted answer kind of did that, he did not mention that you cannot append the entire array at once, which was the main problem for me.

Estimate answered 11/10, 2022 at 5:27 Comment(0)
D
0

For anyone stumbling upon this in 2024, I don't know if this is because of the age of accepted answer (3 years ago), but as of today, upload.array('pictures') can be used without the need to provide a number of files. This way is probably better security-wise, since the user is not allowed to pass any and all files over the wire (that would be the case with upload.any() , only the ones with the field name specified.

In my case, I needed to upload multiple audio files. I acquired them from multiple modals on the frontend with this function (using jQuery):

/**
 * Gets the audio file array from the audio file inputs.
 * @returns {File[]|null}
 */
function getAudioFilesFromUploadModal () {
    const audioFileInputs = $('.audio-file-input');
    if (audioFileInputs.length) {
        // Get all files from the DOM into the file array
        let files = [];
        for (let singleAudioFileInput of audioFileInputs) {
            if (singleAudioFileInput?.files[0]) {
                files.push(singleAudioFileInput.files[0]);
            }
        }
        if (files && files.length) {
            return files;
        } else {
            return null;
        }
    } else {
        return null;
    }
}

The inputs were something akin to this:

<div class="custom-file">
   <input type="file"
          class="audio-file-input"
          name="audioFiles"
          id="audioFileInput{{id}}">
   <label class="custom-file-label"
          for="audioFileInput{{id}}">
       Choose file
   </label>
</div>

The form data object that was sent through the POST request was:

let irrelevantStrings = getIrrelavantStrings();

let audioFileArray = getAudioFilesFromUploadModal();
let postData = {
  irrelevantStrings: irrelevantStrings,
  audioFileArray: audioFileArray
};
let formData = new FormData();
for (let key in postData) {
  // If key is 'audioFiles' push File objects as *individual files* on FormData.
  if (key === 'audioFileArray') {
      for (let currentAudioFile of postData[key]) {
          // Use the same key name as the one filtered through multer
          formData.append('audioFile', currentAudioFile);
      }
  } else {
      formData.append(key, postData[key]);
  }
}

I believe the key here is that multer does not accept FileList objects through upload.array() like I was previously trying to pass. It accepts a number of individual files with the same name.

Donato answered 28/6 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.