You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用react-hook-form上传文件时req.file为空的问题求助

问题:FormData上传文件时req.file为空,返回“No files uploaded”错误

我尝试结合react-hook-form与验证系统,通过append()构建FormData,再用fetch请求将数据存入MongoDB,但始终出现“No files uploaded”错误,后端req.file为空或未定义。以下是相关代码,预期能正确构建数据对象并POST至MongoDB表中。


前端ManageRestaurantForm.jsx代码

import React from 'react'
import { FormProvider, useForm } from 'react-hook-form'

// PAGES
import DetailsSection from './DetailsSection'
import CuisinesSection from './CuisinesSection'
import MenuSection from './MenuSection'
import ImageSection from './ImageSection'
import { forumFill } from '../../fetch/Fetch'


const ManageRestaurantForm = () => {

  const formMethods = useForm(); 
    const { handleSubmit } = formMethods; 

    
    const onSubmit = handleSubmit(async (formDataJson) => {
      try {
          
          const formData = new FormData();

          formData.append("restaurantName", formDataJson.restaurantName);
          formData.append("city", formDataJson.city);
          formData.append("country", formDataJson.country);
      
          formData.append(
            "deliveryPrice",
            (formDataJson.deliveryPrice * 100).toString());

          formData.append(
            "estimatedDeliveryTime",
            formDataJson.estimatedDeliveryTime.toString());
          
          formData.append("cuisines", formDataJson.cuisines)

          formDataJson.menuItems.forEach((menuItem, index) => {
            formData.append(`menuItems[${index}][name]`, menuItem.name);
            formData.append(
              `menuItems[${index}][price]`,
              (menuItem.price * 100).toString()
            );
          });

        console.log(formDataJson.menuItems)
       formDataJson.menuItems.forEach((menuItem, index) => {
        formData.append(`menuItems[${index}][name]`, menuItem.name);
        formData.append(
          `menuItems[${index}][price]`,
          (menuItem.price * 100).toString()
        );
      });

      if (formDataJson.imageFiles && formDataJson.imageFiles.length > 0) {
        Array.from(formDataJson.imageFiles).forEach((file, index) => {
          formData.append(`imageFiles[${index}]`, file);
        });
      }
      
      console.log(formData)
        await forumFill("http://localhost:7000/api/my/restaurant", formData);

        } catch (err) {
          console.log(err)
          
        }
    })


  return (
    <div>
      <FormProvider {...formMethods}>
            <form onSubmit={onSubmit}>

            {/* FORM COMPONENTS */}

          <DetailsSection />
          <CuisinesSection/>
          <MenuSection />
          <ImageSection />


            <span className='flex justify-end'>
                      <button type="submit" className='bg-red-600  text-white h-full mb-10 w-40 rounded hover:rounded-md hover:duration-300 duration-300 p-2 font-bold hover:bg-red-500 cursor-pointer text-2xl'>
                          Save
                     </button>
            </span>
            </form>
      </FormProvider>
    </div>
  )
}

export default ManageRestaurantForm

forumFill函数代码

// forms/manage-restaurant
export const forumFill = async (url, data) => {

    try {
      const formDataJSON = JSON.stringify(data); 
    const rawResponse = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: formDataJSON 
    });
      const content = await rawResponse.json();
  
    console.log(formDataJSON)
    console.log(content);
    } catch (error) {
      console.log(error)
    } 
  
};

后端控制器代码

const cloudinary = require("cloudinary").v2;
const mongoose = require("mongoose");
const { Restaurant } = require("../mongodb/models/restaurant");

class Controller {
    async createMyRestaurant(req, res) {
        try {

            if (!req.file || req.file.length === 0) {
                return res.status(400).json({ message: "No files uploaded" });
            }

            const existingRestaurant = await Restaurant.findOne({ user: req.userId });

            if (existingRestaurant) {
                return res.status(409).json({ message: "User restaurant already exists" });
            }

            const imageFile = req.File;
            
            console.log(`Your imageFile is ${imageFile}`)

            const b64 = Buffer.from(imageFile.buffer).toString("base64");
            const dataURI = "data:" + imageFile.mimetype + ";base64," + b64;
            const uploadResponse = await cloudinary.uploader.upload(dataURI);
            const imageUrl = uploadResponse.url;

            console.log(`Your imageUrl is ${imageUrl}`)

            const restaurant = new Restaurant({
                body: req.body,
                lastUpdate: new Date(),
            });

            await restaurant.save();

            res.status(201).send(restaurant);
        } catch (err) {
            console.log(err);
            res.status(500).json({ message: "Something went wrong :(" });
        }
    }
}

module.exports = Controller;

问题根源及修复方案

1. Fetch请求错误处理FormData

你在forumFill函数里把FormData转成JSON字符串,还手动设置Content-Type: application/json,但FormData必须直接作为请求体发送,浏览器会自动添加正确的multipart/form-data头。

修改后的forumFill函数:

export const forumFill = async (url, data) => {
  try {
    const rawResponse = await fetch(url, {
      method: 'POST',
      // 移除手动设置的Content-Type,浏览器自动处理
      body: data 
    });
    const content = await rawResponse.json();
    console.log(content);
  } catch (error) {
    console.log(error);
  }
};

2. 后端文件获取与检查错误

  • 后端代码里写了req.File(大写F),正确应该是req.file(小写);
  • 前端上传的是多文件(imageFiles[${index}]),后端需要用req.files获取,而不是req.file;
  • 文件检查逻辑要对应多文件场景。

3. 前端冗余代码

代码中重复循环menuItems添加到FormData,删掉其中一次即可。

4. 后端Restaurant对象构建错误

直接传body: req.body不符合模型字段规范,需要展开req.body的字段,同时关联用户ID和图片链接。

5. 后端需配置Multer处理文件上传

必须用multer中间件解析multipart/form-data请求,示例配置:

const multer = require('multer');
// 内存存储,方便转base64上传到Cloudinary
const upload = multer({ storage: multer.memoryStorage() });

// 路由中使用,接收名为imageFiles的多文件
router.post('/api/my/restaurant', upload.array('imageFiles'), controller.createMyRestaurant);

修改后的后端控制器关键代码

async createMyRestaurant(req, res) {
  try {
    // 检查多文件是否存在
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ message: "No files uploaded" });
    }

    const existingRestaurant = await Restaurant.findOne({ user: req.userId });
    if (existingRestaurant) {
      return res.status(409).json({ message: "User restaurant already exists" });
    }

    // 批量上传图片到Cloudinary
    const uploadPromises = req.files.map(file => {
      const b64 = Buffer.from(file.buffer).toString("base64");
      const dataURI = `data:${file.mimetype};base64,${b64}`;
      return cloudinary.uploader.upload(dataURI);
    });
    const uploadResponses = await Promise.all(uploadPromises);
    const imageUrls = uploadResponses.map(item => item.url);

    // 正确构建Restaurant实例
    const restaurant = new Restaurant({
      ...req.body,
      user: req.userId,
      imageUrls: imageUrls, // 假设模型中有imageUrls字段存储图片链接
      lastUpdate: new Date(),
    });

    await restaurant.save();
    res.status(201).send(restaurant);
  } catch (err) {
    console.log(err);
    res.status(500).json({ message: "Something went wrong :(" });
  }
}

内容的提问来源于stack exchange,提问作者With Me

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:03:11