React前端提交带图片表单至后端报错400:images字段不被允许
React前端提交图片到Multer后端报错的解决方案
问题描述
在React前端将图片加入postValues后发送至后端接口,后端的multer、路由、控制器及发布服务通过Insomnia测试正常,但前端表单提交图片时出现400错误,提示"images" is not allowed。打印postValues可见图片数据格式正确,但提交方式存在问题。
相关代码
前端代码
/* <----------------------- MODULOS --------------------------> */ import Creatable, { useCreatable } from 'react-select/creatable'; /* <----------------------- FUNCIONES --------------------------> */ import { useState, useEffect } from "react"; /* <---------------- COMPONENTES MATERIAL UI -------------------> */ import Avatar from '@mui/material/Avatar'; /* <----------------------- ICONOS --------------------------> */ import Button from '@mui/material/Button'; import SendIcon from '@mui/icons-material/Send'; /* <----------------------- SERVICIOS -----------------------< */ import { getHashtags } from "@/services/hashtag.service"; import { createPost } from "@/services/post.service"; /* <----------------------- CONTEXTO -----------------------< */ import { useAuth } from '@/context/AuthContext.jsx'; const CreatePost = () => { const { user } = useAuth(); const [postValues, setPostValues] = useState({ author: user.id, title: "", description: "", images: [], hashtags: [] }); const [hashtagValues, setHashtagValues] = useState(""); const getDataHashtags = async () =>{ try { const response = await getHashtags(); const optionsHashtags = response.data.data.data.map(hashtag => ({ // No se utiliza el id, por como se programo en el backend -> value: hashtag._id, value: hashtag.nameHashtag, label: hashtag.nameHashtag })) setHashtagValues(optionsHashtags); } catch (error) { console.log(error); } } useEffect( () => { getDataHashtags(); },[]) // Manejo de campos simples const handleChange = (e) => { const { name, value } = e.target; setPostValues( (prevState) => ({ ...prevState, [name]: value }) ) } // Manejo de hashtags const handleHashtagChange = (selectedOptions) => { const hashtags = selectedOptions ? selectedOptions.map(option => option.value) : []; setPostValues((prevState) => ({ ...prevState, hashtags })); }; const handleImageChange = (e) => { const files = Array.from(e.target.files); setPostValues((prevState) => ( {...prevState, images: [...prevState.images, ...files]} )); } // Envio de postValues const onSubmit = async (e) => { e.preventDefault(); try { await createPost(postValues); } catch (error) { console.log(error); } }; return ( <div className='flex flex-row p-4 w-3/4 mx-auto space-x-2'> <div> <Avatar/> </div> <form className="flex flex-col w-full" encType="multipart/form-data"> <input placeholder='Titulo' name="title" value={postValues.title} onChange={handleChange} className='bg-gray-100 p-2 mb-4 border-2 rounded-md leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 focus:outline-none focus:border-blue-500 flex w-full'> </input> <textarea id="postContent" rows="4" placeholder="Cuentanos más sobre lo que quieres publicar :-)" onChange={handleChange} name="description" value={postValues.description} className="bg-gray-100 p-2 mb-2 rounded-md px-4 py-2 leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 resize-none focus:outline-none focus:border-blue-500" > </textarea> <Creatable isMulti placeholder="Hashtags" onChange={handleHashtagChange} className="bg-gray-100 mb-2 rounded-md leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 focus:outline-none focus:border-blue-500" options= {hashtagValues} > </Creatable> <div className="mb-2"> <div className="mb-2"> <input type="file" multiple onChange={handleImageChange} /> </div> </div> <div className="grid grid-cols-2 gap-2 mb-4"> {postValues.images.map((image, index) => ( <div key={index} className="relative"> <img src={URL.createObjectURL(image)} alt={`upload-${index}`} className="w-full h-full object-cover rounded-md" /> </div> ))} </div> <Button variant="contained" endIcon={<SendIcon />} className="rounded" onClick={onSubmit} > Publicar </Button> </form> </div> ) } export default CreatePost
后端路由
router.post("/createPost",[uploadImage.array('images')], postController.createPost);
Multer配置
/* <---------------------- MODULES ---------------------- ----> */ const multer = require('multer'); // storage: Memory storage configuration const storage = multer.memoryStorage(); // limits: File size limit (5MB) const limits = { fileSize: 1024 * 1024 * 5 }; // filter: Filter function to allow only image files (PNG, JPEG, JPG) const filter = (req, file, cb) => { if(file && (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/jpg')){ cb(null, true); }else{ cb(null, false); } } // Multer configuration const uploadImage = multer({ storage: storage, fileFilter: filter, limits }); module.exports = uploadImage;
报错信息
AxiosError { message: 'Request failed with status code 400', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {...}, request: XMLHttpRequest, response: { config: {...}, data: { state: 'Error', message: '"images" is not allowed', details: {...} }, headers: AxiosHeaders { content-length: '68', content-type: 'application/json; charset=utf-8' }, request: XMLHttpRequest, status: 400, statusText: 'Bad Request', [[Prototype]]: Object }, stack: 'AxiosError: Request failed with status code 400\n at settle (webpack-internal:///./node_modules/axios/lib/core/settle.js:24:12)\n at XMLHttpRequest.onloadend (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:125:66)\n at Axios.request (webpack-internal:///./node_modules/axios/lib/core/Axios.js:54:41)\n at async onSubmit (webpack-internal:///./src/components/CreatePost.jsx:80:13)', [[Prototype]]: Error }
问题原因
直接通过axios发送包含File对象的JSON数据时,File会被序列化为空对象{},后端Multer无法识别到文件,同时后端的请求验证(如Joi/Express Validator)会认为images字段格式不符合要求,因此返回"images" is not allowed的错误。
解决方案
必须使用FormData格式发送文件和表单数据,这是Multer能正确解析文件的唯一格式。
1. 修改前端提交逻辑
更新onSubmit函数,将postValues转换为FormData:
const onSubmit = async (e) => { e.preventDefault(); try { const formData = new FormData(); // 追加普通字段 formData.append('author', postValues.author); formData.append('title', postValues.title); formData.append('description', postValues.description); // 追加数组类型的hashtags(后端需支持数组解析) postValues.hashtags.forEach(hashtag => { formData.append('hashtags[]', hashtag); }); // 追加图片,对应Multer的array('images')配置 postValues.images.forEach(image => { formData.append('images', image); }); // 发送FormData await createPost(formData); } catch (error) { console.log(error); } };
2. 调整Axios请求配置
确保createPost服务中不手动设置Content-Type,Axios会自动为FormData添加正确的multipart/form-data头及边界标识:
// post.service.js import axios from 'axios'; export const createPost = async (formData) => { return await axios.post('/api/posts/createPost', formData, { headers: { // 不要手动设置Content-Type,让Axios自动处理 // 'Content-Type': 'multipart/form-data' ❌ 移除这行 } }); };
3. 可选:后端数组字段解析
如果后端无法直接解析hashtags[]数组,可在后端使用express.urlencoded({ extended: true })中间件,确保能正确接收数组类型的表单数据。
内容的提问来源于stack exchange,提问作者Matias San Martin M.
相关产品推荐
相关产品推荐

