skip to Main Content

this is Omar,

Note: Too much talk? the sections below not all of them is a must to check if you are just trying to understand the problem, just see these ones:

  1. Front End (A really quick look at the code)
  2. Back End (You can check if you want, but it is just standard setup for multer)
  3. Postman (A quick look at request details, and dont look at its code)
  4. Network tab (Check it)
  5. What Happened in the Backend (understand the problem)

Front End

my problem is when sending FormData using fetch in my front-end (react):

const formData = new FormData();
formData.append('file', values.image);
formData.append('path', 'image');
formData.append('model', 'item');
formData.append(
  'data',
  JSON.stringify({
    menu: menu._id.toString(),
    item: values.item,
  })
);

const req = new Request(`/api/v1/upload`, {
  method: 'POST',
  body: formData,
});

The Request constructor is the exact same as fetch, but it just skips doing response.json() everytime, Request class is my own (I built it)

Back End

Backend code is using multer for multipart formData.
Backend perfectly works fine with postman, so using postman to send the request works just fine, but using fetch as up does not.
for the backend code, it is just simple, i use upload.single, and memoryStorage.
Code:

const storage = multer.memoryStorage();

const upload = multer({ storage, fileFilter });

exports.processImage = (req, res, next) => {
  if (!req.file) return next();

  req.file.filename =
    req.modelController.createFileName(req, req.file) + '.jpeg';

  sharp(req.file.buffer)
    .toFormat('jpeg')
    .toFile(
      path.join(
        `public/image/upload/${req.body.model.toLowerCase()}`,
        req.file.filename
      )
    );

  next();
};

Again it works fine if i use postman

Postman

Postman request image
Postman code is really similar to my code:

const formdata = new FormData();
formdata.append("model", "item");
formdata.append("path", "image");
formdata.append("data", "{"menu": "673e442bae0dd4717f4af4c1", "item": "673f76230fb7a35ddfc2cd57"}");
formdata.append("file", fileInput.files[0], "/C:/Users/LENOVO/Downloads/Matrix-Cola-Carbonated-Drink.jpg");

const requestOptions = {
  method: "POST",
  headers: myHeaders,
  body: formdata,
  redirect: "follow"
};

fetch("http://127.0.0.1:5423/api/v1/upload/", requestOptions)
  .then((response) => response.text())
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

What Happens in the Backend?

It receives the body as null Object, even using multer, and the file as undefined

Network Tab

network tabs shows the following
Network tab, request payload
Headers are going well, content type is multipart/form-data.

What did i try?

I did not try many things, but:

Content type

Setting the content type header is done automatically by form data instance

What is Values.image?

it is [Object File]

What form do i use

i use formik to handle forms.
but am sure that the file is being saved at values.image

2

Answers


  1. Chosen as BEST ANSWER

    I've solved the CASE!!

    For anyone who is getting this problem, please follow these EASY steps:

    Steps:

    -Check if you have the same thing as i do: My problem wasnt that backend does not receive the formData, the problem was that fileFilter does not receive formData, because multer does not offer req.body nor req.file with it, how to check if that is your problem? go to fileFilter function, and log the body, and then go to processImage function and log the body, if it logs in processImage, but not in fileFilter, then you have my problem, if it doesnt log for both, I dont think that my solution will work for you

    -How to solve this problem? go to your front-end, appending data like this: formData.append('file', values.file); formData.append('textContent', 'textContent'); will not work, you must make the append function for file field at the end, as below: formData.append('textContent', 'textContent'); formData.append('file', values.file); And your problem have been solved. Congrats!

    Why does this problem happen?

    simply because postman sends the data of the form as one piece, and the file field is at the end, while front end, you made file field first, so while multer process the file field, it runs fileFilter, so fileFilter does not gets the body, nor the file, while when sending body first then file, the body gets processed, so fileFilter Thanks for everyone. By the way this case is answered before, but people who gets into this problem does not know that this is same problem as this.


  2. Your frontend code using Request is closely related to fetch because the Request object is designed to be used with the Fetch API. The Request class itself doesn’t perform the actual network operation—it simply creates a Request object that you typically pass to fetch.

    The problem arises because your backend isn’t correctly receiving the multipart FormData payload when using fetch.

    The issue is related to the Fetch API because your custom Request class likely wraps it. The root cause is the handling of the Content-Type header and how FormData is passed. You can resolve this by ensuring the Request class or fetch is used correctly or by switching to a library like axios for consistency.

    axios.post inspects the FormData and sets the Content-Type header with the appropriate boundary (a required part of the multipart/form-data format).
    This ensures the server correctly interprets the form data.

    Demo

    server.js

    const express = require('express');
    const cors = require('cors');
    const multer = require('multer');
    const sharp = require('sharp');
    const path = require('path');
    const fs = require('fs');
    
    // Initialize the Express app
    const app = express();
    
    // Middleware for JSON parsing and CORS
    app.use(express.json());
    app.use(cors());
    
    // Configure multer storage in memory
    const storage = multer.memoryStorage();
    const upload = multer({ storage });
    
    // Ensure upload directories exist
    const ensureUploadDirExists = (dirPath) => {
      if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath, { recursive: true });
      }
    };
    
    // POST route to handle file uploads
    app.post('/api/v1/upload', upload.single('file'), async (req, res) => {
      try {
        if (!req.file) {
          return res.status(400).json({ error: 'No file uploaded.' });
        }
    
        const { path: uploadPath, model, data } = req.body;
    
        // Parse and log the additional data
        const parsedData = JSON.parse(data);
        console.log('Parsed data:', parsedData);
    
        // Ensure the directory exists
        const uploadDir = path.join(__dirname, 'public', uploadPath, model.toLowerCase());
        ensureUploadDirExists(uploadDir);
    
        // Create a unique filename
        const filename = `${Date.now()}-${model.toLowerCase()}.jpeg`;
    
        // Process and save the image
        await sharp(req.file.buffer)
          .toFormat('jpeg')
          .toFile(path.join(uploadDir, filename));
    
        // Respond with the file path and other details
        res.json({
          success: true,
          message: 'File uploaded successfully.',
          filePath: `/public/${uploadPath}/${model.toLowerCase()}/${filename}`,
          data: parsedData,
        });
      } catch (error) {
        console.error('Error processing upload:', error);
        res.status(500).json({ success: false, error: 'Failed to process upload.' });
      }
    });
    
    // Server listening
    const PORT = process.env.PORT || 5423;
    app.listen(PORT, () => {
      console.log(`Server running on port ${PORT}`);
    });
    

    Dependency Install for server

    npm install express cors multer sharp
    

    In frontend
    App.js

    import React, { useState } from 'react';
    import axios from 'axios';
    
    const App = () => {
        const menu = { _id: '12345' };
        const [imageFile, setImageFile] = useState(null); // File input
        const [itemName, setItemName] = useState(''); // Item name input
        const [responseMessage, setResponseMessage] = useState('');
    
        // Handle file input change
        const handleFileChange = (event) => {
            setImageFile(event.target.files[0]);
        };
    
        const handleSubmit = async (event) => {
            event.preventDefault();
    
            if (!imageFile) {
                setResponseMessage('Please select an image to upload.');
                return;
            }
    
            const formData = new FormData();
            formData.append('file', imageFile);
            formData.append('path', 'image');
            formData.append('model', 'item');
            formData.append(
                'data',
                JSON.stringify({
                    menu: menu._id.toString(),
                    item: itemName,
                })
            );
    
            try {
                const response = await axios.post('http://localhost:5423/api/v1/upload', formData, {
                    headers: { 'Content-Type': 'multipart/form-data' },
                });
                console.log(response.data);
                setResponseMessage(JSON.stringify(response.data, null, 2)); // Format JSON response
            } catch (error) {
                console.error('Error details:', error.response?.data || error.message);
                setResponseMessage('Error: Could not get a response.');
            }
        };
    
        return (
            <div>
                <form onSubmit={handleSubmit}>
                    <div>
                        <label>
                            Upload Image:
                            <input type="file" onChange={handleFileChange} />
                        </label>
                    </div>
                    <div>
                        <label>
                            Item Name:
                            <input
                                type="text"
                                value={itemName}
                                onChange={(e) => setItemName(e.target.value)}
                            />
                        </label>
                    </div>
                    <button type="submit">Submit</button>
                </form>
                {responseMessage && (
                    <div>
                        <h3>Server Response:</h3>
                        <pre>{responseMessage}</pre>
                    </div>
                )}
            </div>
        );
    };
    
    export default App;
    

    Dependency Install

    npm install axios
    

    Result

    node server.js
    enter image description here

    npm start
    enter image description here

    Postman
    enter image description here

    Updating step by step

    0. Checking npx version and others

    npx --version
    npm --version
    node --version
    

    enter image description here

    1. Create Frontend

    npx create-react-app frontend
    

    result checking by tree

    cd frontend
    tree /F
    

    enter image description here

    2. Install axios

    npm install axios
    

    result the package.json

    {
      "name": "frontend",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@testing-library/jest-dom": "^5.17.0",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "axios": "^1.7.7",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "react-scripts": "5.0.1",
        "web-vitals": "^2.1.4"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
      "eslintConfig": {
        "extends": [
          "react-app",
          "react-app/jest"
        ]
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      }
    }
    

    3. Replace App.js with my code

    enter image description here

    4. Run server.js

    npm install express cors multer sharp
    node server.js
    

    enter image description here

    5. Run frontend

    At frontend directory

    npm start
    

    enter image description here

    6. Got response from server

    enter image description here

    enter image description here

    Update 2 using Request()

    import React, { useState } from 'react';
    
    const App = () => {
        const menu = { _id: '12345' };
        const [imageFile, setImageFile] = useState(null); // File input
        const [itemName, setItemName] = useState(''); // Item name input
        const [responseMessage, setResponseMessage] = useState('');
    
        // Handle file input change
        const handleFileChange = (event) => {
            setImageFile(event.target.files[0]);
        };
    
        const handleSubmit = async (event) => {
            event.preventDefault();
    
            if (!imageFile) {
                setResponseMessage('Please select an image to upload.');
                return;
            }
    
            const formData = new FormData();
            formData.append('file', imageFile);
            formData.append('path', 'image');
            formData.append('model', 'item');
            formData.append(
                'data',
                JSON.stringify({
                    menu: menu._id.toString(),
                    item: itemName,
                })
            );
    
            try {
                // Create a new Request object
                const req = new Request('http://localhost:5423/api/v1/upload', {
                    method: 'POST',
                    body: formData,
                });
    
                // Use fetch with the Request object
                const response = await fetch(req);
    
                // Parse and handle the response
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
    
                const responseData = await response.json();
                console.log(responseData);
                setResponseMessage(JSON.stringify(responseData, null, 2)); // Format JSON response
            } catch (error) {
                console.error('Error details:', error.message);
                setResponseMessage('Error: Could not get a response.');
            }
        };
    
        return (
            <div>
                <form onSubmit={handleSubmit}>
                    <div>
                        <label>
                            Upload Image:
                            <input type="file" onChange={handleFileChange} />
                        </label>
                    </div>
                    <div>
                        <label>
                            Item Name:
                            <input
                                type="text"
                                value={itemName}
                                onChange={(e) => setItemName(e.target.value)}
                            />
                        </label>
                    </div>
                    <button type="submit">Submit</button>
                </form>
                {responseMessage && (
                    <div>
                        <h3>Server Response:</h3>
                        <pre>{responseMessage}</pre>
                    </div>
                )}
            </div>
        );
    };
    
    export default App;
    

    Result : Same result using "Request()"

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search