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

React表单含图片时无法保存至MongoDB问题求助

React+Node+MongoDB 带图片的帖子提交失败问题排查与解决

问题背景

开发了一套包含React前端和Node.js后端的帖子系统,前端通过表单收集帖子数据(含base64格式图片),经Redux添加Auth Token后,通过Axios提交到后端API,最终存储至MongoDB。

  • 提交不含图片的表单时,数据可正常保存到数据库;
  • 提交包含base64图片的表单时,帖子无法保存,后端Controller未打印req.body数据,且无任何报错信息。

相关代码文件

NewPost.jsx(前端表单组件)

import React from "react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import Spinner from "../../components/Spinner";
import { toast } from "react-toastify";
import SideNav from "../../components/navigation/SideNav";
import FileBase from "react-file-base64";
import FormInput from "../../components/forms/FormInput";
import FormButton from "../../components/forms/FormButton";
import Editor from "../../components/forms/Editor";
import { createPost, reset } from "../../features/post/postSlice";

const Posts = () => {
  const [formData, setFormData] = useState({
    title: "",
    description: "",
    body: "",
    image: "",
  });

  const { title, description, body, image } = formData;

  const navigate = useNavigate();
  const dispatch = useDispatch();

  const { posts, isLoading, isError, isSuccess, message } = useSelector(
    (state) => state.posts
  );

  useEffect(() => {
    if (isError) {
      toast.error(message);
    }

    if (isSuccess) {
      navigate("/posts");
    }

    dispatch(reset());
  }, [posts, isError, isSuccess, message, navigate, dispatch]);

  const onSubmit = (e) => {
    e.preventDefault();
    const postData = {
      title,
      description,
      body,
      image,
    };

    console.log("page")
    console.log(postData)
    dispatch(createPost(postData));
  };

  if (isLoading) {
    return <Spinner />;
  }

  return (
    <div className="flex text-sm h-screen">
      
      <SideNav />
      {/* Main Content */}
      <div className="w-3/6 bg-gray-400 shadow-2xl rounded-lg mx-auto text-center py-12 mt-2 rounded-xl">
        <div className="flex items-center justify-center">
          <div className="text-3xl text-gray-900">New Post</div>
        </div>

        {/* main */}
        <div className="p-10 flex flex-col justify-center">
          <form onSubmit={onSubmit}>
            <div className="mb-4">
              {/* tilte */}
              <FormInput
                label="Title"
                name="title"
                type="text"
                value={title}
                placeholder="Post title"
                onChange={(e) =>
                  setFormData({ ...formData, title: e.target.value })
                }
                required={true}
              />
            </div>

            <div className="mb-4">
              {/* Cover image */}
              <FileBase
                name="image"
                type="file"
                multiple={false}
                onDone={({ base64 }) =>
                  setFormData({ ...formData, image: base64 })
                }
              />
            </div>
            <div className="mb-4">
              {/* Description */}
              <FormInput
                label="Decription"
                name="description"
                type="text"
                value={description}
                placeholder="Short Description"
                onChange={(e) =>
                  setFormData({ ...formData, description: e.target.value })
                }
                required={true}
              />
            </div>

            <div className="mb-6">
              <Editor
                name="body"
                label="Body"
                rows="10"
                placeholder="Whats On your mind"
                value={body}
                onChange={(e) =>
                  setFormData({ ...formData, body: e.target.value })
                }
              />
            </div>

            {/* submit button */}
            <div className="m-3 flex items-center justify-between">
              <FormButton type="submit" text="Post" />
            </div>
          </form>
        </div>
      </div>
    </div>
  );
};

export default Posts;

postSlice.js(Redux状态管理)

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import postService from "./postService";

const initialState = {
  posts: [],
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: "",
};

export const createPost = createAsyncThunk(
  "posts/",
  async (postData, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      console.log("Slice")
      console.log(token)
      console.log(postData)
      return await postService.createPost(postData, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const postSlice = createSlice({
  name: "post",
  initialState,
  reducers: {
    reset: (state) => initialState,
  },
  extraReducers: (builder) => {
    builder
      .addCase(createPost.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(createPost.fulfilled, (state) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.posts.push();
      })
      .addCase(createPost.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getPosts.pending, (state) => {
        state.isLoading = true;
      });
  },
});

export const { reset } = postSlice.actions;
export default postSlice.reducer;

postService.js(Axios请求封装)

import axios from "axios";

const API_URL = "http://localhost:5000/api/posts/";

const createPost = async (postData, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  console.log("service")
  console.log(config)
  console.log(postData)
  const response = await axios.post(API_URL, postData, config);

  return response.data;
};


const postService = {
  createPost,
};

export default postService;

postController.js(后端接口逻辑)

const asyncHandler = require('express-async-handler');
const Post = require('../models/postModel');
const User = require('../models/userModel');

// @desc create a post
// @route POST /api/posts/
// @access Private
const createPost = asyncHandler(async (req,res) => {
    console.log("controller")
    console.log(req.body)
    if(!req.body.body){
        res.status(400);
        throw new Error('Please fill in all fields');
    }

    const post = await Post.create({
        user: req.user.id,
        title: req.body.title,
        description: req.body.description,
        body: req.body.body,
        image: req.body.image
    });

    res.status(200).json({message: 'Post created', post: post});
});

module.exports = {
    createPost,
}

问题原因分析

从日志链路来看,前端页面、Redux Slice、Axios Service层都能正常打印完整的postData,但后端Controller未输出req.body,说明请求体没有被后端正确解析。核心原因是base64格式的图片数据体积过大,超过了Express默认的请求体大小限制,导致请求被截断,后端无法获取到完整的请求数据。

解决方案

1. 调整Express请求体大小限制

在后端入口文件(如server.js或app.js)中,修改express.json()的配置,增大允许的请求体体积:

const express = require('express');
const app = express();

// 设置允许的最大请求体大小为10MB(可根据实际图片大小调整)
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

2. 优化Axios请求配置

在postService.js中添加请求体大小限制,并完善错误捕获:

const createPost = async (postData, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    maxBodyLength: Infinity,
    maxContentLength: Infinity
  };

  try {
    console.log("service")
    console.log(config)
    console.log(postData)
    const response = await axios.post(API_URL, postData, config);
    return response.data;
  } catch (error) {
    console.error('请求失败详情:', error.response?.data || error.message);
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString();
    throw new Error(message);
  }
};

3. 验证MongoDB字段类型

确保postModel.js中image字段的类型为String,适配base64字符串格式:

const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  title: {
    type: String,
    required: true
  },
  description: {
    type: String,
    required: true
  },
  body: {
    type: String,
    required: true
  },
  image: {
    type: String,
    default: ''
  }
});

module.exports = mongoose.model('Post', postSchema);

4. 补充后端请求体校验

在Controller中添加请求体存在性校验,避免因请求截断导致的逻辑异常:

const createPost = asyncHandler(async (req,res) => {
    console.log("controller")
    console.log(req.body)
    
    if (!req.body) {
        res.status(400);
        throw new Error('请求体为空,请检查请求数据');
    }
    
    if(!req.body.body){
        res.status(400);
        throw new Error('Please fill in all fields');
    }

    const post = await Post.create({
        user: req.user.id,
        title: req.body.title,
        description: req.body.description,
        body: req.body.body,
        image: req.body.image
    });

    res.status(200).json({message: 'Post created', post: post});
});

内容的提问来源于stack exchange,提问作者kingston-5

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:19:54