2

I'm submitting a form on an Angular build front-end. The form has both normal text input fields and a file upload function. I POST both the text input fields to my NodeJS API as a JSON object "contact", as well as the file as a new FormData as such:

// 'contact' defined above as a JSON object
// 'profilePic' set from event.target.files[0] in a listener function

const profilePicData = new FormData();
profilePicData.append('file', profilePic);

            return this.http
                .post<ContactResponseData>('API_URL_HERE',
                { contact, profilePicData } ...

And then capture it from my API as such:

router.post("/", upload.single('file'),(req, res) => {

    console.log("REQ: "+ req);

    console.log("BODY: " + JSON.stringify(req.body));
    console.log("FILE: " + req.file);

The req.file is "undefined", i.e. null, and my req.body has a "profilePicData" key value pair which is empty. I assume this is because the entire form gets submitted as JSON and not as multipart form data.

But I can't google anything helpful around how to send both JSON and multipart to my API as one POST request so that both req.body and req.file pick up the right information. I guess understanding the theory and best practices behind what's going here is what I'm after. Should I have two POST urls, one for JSON and one for file? Or should I be submitting my JSON as multipart as well (how do I do that in Angular)? Any help is appreciated.

glog
  • 809
  • 2
  • 9
  • 19

2 Answers2

2

You will have to send everything as multipart by adding fields to an instance of FormData and send it as the payload.

const form = new FormData();
form.append('file', profilePic);
form.append('contact', contact);

...
return this.http.post<ContactResponseData>('API_URL_HERE', form, ...)
thammada.ts
  • 5,065
  • 2
  • 22
  • 33
0

I used the below method in React

Below is how I created input

<form className={styles.root} noValidate autoComplete="off">
    <input
        name="avatar_image" // name of input field or fieldName simply
        enctype="multipart/form-data"
        type="file"
        onChange={(event) => {
            // console logging selected file from menu
            console.log( event.target.files[0] ) // gives first file
            // setState method with event.target.files[0] as argument
            this.setState(prev => ({...prev, user_image: event.target.files[0]}))
        }}
    />
</form>

Below is how I made requests to backend

const formData = new FormData()
formData.append('user_name', this.state.user_name)
formData.append('phone_number', this.state.phone_number)
// now below avatar_image is the fieldName of the image, then comes the file to upload, and the file name in the end
formData.append('avatar_image', this.state.user_image, this.state.user_image.name)

axios.post(utils.baseUrl + '/avatar-uploads/avatar-image-upload', formData, {
    onUploadProgress: progressEvent => {
        console.log( 'upload progress: ' + Math.round((progressEvent.loaded / progressEvent.total)*100) + '%' )
    }
})
.then(function (response) {
    // further code
})
.catch(function (error) {
    console.log(error);
}); 

Below is how I handled the backned with multer, including dealing with payload

const image_storage = multer.diskStorage({
    destination: path.join(__dirname , '../../assets/images/uploads/avatar_image'),
    filename: function(req, file, cb){
        cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
    }
});

// Init Upload
const user_avatar_image_upload = multer({
    storage: image_storage,
    limits:{fileSize: 2000000}, // 1 mb
    fileFilter: function(req, file, cb){
        checkFileTypeForUserAvatar(file, cb);
    }
}).single('avatar_image'); // this is the fieldName that will be dealt

// Check File Type
function checkFileTypeForUserAvatar(file, cb){
    // Allowed ext
    let filetypes = /jpeg|jpg|png|gif/;
    // Check ext
    let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
    // Check mime
    let mimetype = filetypes.test(file.mimetype);

    if(mimetype && extname){
        return cb(null,true);
    } else {
        cb('Error: jpeg, jpg, png, gif Images Only!');
    }
}

// router.post('/protected-avatar-image-upload', passport.authenticate('jwt', { session: false }), (req, res, next) => {
router.post('/avatar-image-upload', (req, res, next) => {

    console.log(req.body) // here the req.body will turn out {}

    user_avatar_image_upload(req, res, (err) => {
        if(err){
            console.log(err)
        } else {

            if(req.file == undefined){

                res.status(404).json({ success: false, msg: 'File is undefined!',file: `uploads/${req.file.filename}`})

            } else {
                console.log( req.body.user_name ) // here req.body.user_name and others will work
                // further code
                res.status(200).json({ success: true, msg: 'File Uploaded!',file: `uploads/${req.file.filename}`})
            }
        }
    })

})

Hope this helps how to upload a file using multer, with additional payload being passed so that it can be utilized as well with creating database entries or anything else.

  • I'm getting ```MulterError: Unexpected field``` when I try to use this code with ```express.Router()```. Could you please help me to use this with express router? – Sujith S Manjavana May 25 '22 at 09:53
  • @SujithManjavana try to observe my answer and find 2 instances of 'avatar_image', that is the field I set which I added a file to in the formData for uploading, the same 'avatar_image' can be seen in my Multer backend route as well. If you find it useful please vote my answer – Arsalan Ahmad Ishaq May 25 '22 at 18:33
  • Could you please have a look at my question https://stackoverflow.com/questions/72378201/how-to-use-multer-with-express-router – Sujith S Manjavana May 26 '22 at 04:15