admin管理员组

文章数量:1122846

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

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

Share Improve this question asked Nov 22, 2024 at 21:04 The Khateeb DevThe Khateeb Dev 241 bronze badge
Add a comment  | 

2 Answers 2

Reset to default 0

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

npm start

Postman

Updating step by step

0. Checking npx version and others

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

1. Create Frontend

npx create-react-app frontend

result checking by tree

cd frontend
tree /F

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

4. Run server.js

npm install express cors multer sharp
node server.js

5. Run frontend

At frontend directory

npm start

6. Got response from server

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()"

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.

本文标签: