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

React+Node博客应用:创建文章时分类无法存入MongoDB求助

问题:创建文章时分类标签无法存入MongoDB的categories集合

环境:React ^18.2.0、Node.js v14.18.1,开发博客应用时,用户创建新文章的分类标签并未存入MongoDB的categories集合中。


前端Write组件代码

import { useContext, useState } from "react";
import "./write.css";
import { Context } from "../../context/Context";
import NoImage from "../../images/noImage.jpeg"
import { axiosInstance } from "../../config";

import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

export default function Write() {
  const [title, setTitle] = useState("");
  const [desc, setDesc] = useState("");
  const [cats, setCats] = useState([]);
  const [file, setFile] = useState(null);
  const { user } = useContext(Context);

console.log(desc);  

  const handleSubmit = async (e) => {
    e.preventDefault();

    const newPost = {
      username: user.username,
      title,
      desc,
      categories: cats,
    };

    if (file) {
      const data = new FormData();
      const filename = Date.now() + file.name;
      data.append("name", filename);
      data.append("file", file);
      newPost.photo = filename;
      try {
        await axiosInstance.post("/upload", data);
      } catch (err) {}
    }

    try {
      // First, create the categories and post together
      const res = await axiosInstance.post("/posts", newPost);
      window.location.replace("/post/" + res.data._id);
    } catch (err) {
      console.log(err);
    }

  };

    const putCatHandler = (e) => {
      if(e.keyCode !== 32) return 
      //avoid space key at the first letter
      const value = e.target.value.trimStart();

      //add all element
      if(!value.trim()) return 
      setCats([...cats, value]);
      
      e.target.value = "";
      
    }

    const deleteCat = (index) => {
        setCats(cats.filter((cat, i) => i !== index));
    }

  
  return (
    <div className="writeWrapper">
      <div className="write">
        {/* box1 */}
        {
          !file ? <img className="writeNoImg" src={NoImage} alt="" />
          : <img className="writeImg" src={URL.createObjectURL(file)} alt="" />
        }

        {/* box2 */}
        <form className="writeForm" onSubmit={handleSubmit}>

          <div className="writeFormGroup">
            <input
              type="file"
              id="fileInput"
              onChange={(e) => setFile(e.target.files[0])}
              required={true}
            />
            <input
              type="text"
              placeholder="Title..."
              className="writeInput"
              autoFocus={true}
              onChange={e=>setTitle(e.target.value)}
              required={true}
              autoCapitalize={true}
            />
          </div>

          <div className="writeFormGroup">
                {
                    cats.map((cat, index) => (
                        <div className="catInputItem" key={index}>
                            <span className="text">{cat}</span>
                            <span className="close" onClick={() => deleteCat(index)}>&times;</span>
                        </div>
                    ))
                }
                <input type="text" onKeyDown={putCatHandler} autoCapitalize={true} disabled={cats.length >= 5 ? true : false} required={true}  className="catsUserInput" style={cats.length >= 5 ? {marginBottom:'15px'} : {}} placeholder="Press space key after typing category..."/>
                {
                  cats.length >= 5 ? (<span style={{ color: "red"}}>You can put only 5 categories</span>)
                                   : <></> 
                }
          </div>

          <div className="writeFormGroup">
            <ReactQuill theme="snow" className="editor" value={desc} onChange={setDesc} />
          </div>
          
          <div className="writeFormGroup">
            <button className="writeSubmit" type="submit">
              Publish
            </button>
          </div>
          
        </form>
      </div>
      
    </div>
  );
}

后端posts路由代码

const router = require("express").Router();
const User = require("../models/User.js");
const Post = require("../models/Post.js");
const Category = require("../models/Category.js");
const bcrypt = require("bcrypt");

//Create post
router.post("/", async (req, res) => {
    const { categories, ...postData } = req.body;
  
    try {
      // 1. Find or create categories
      const categoryPromises = categories.map(async (catName) => {
        const existingCategory = await Category.findOne({ name: catName });
        if (existingCategory) {
          return existingCategory;
        } else {
          const newCategory = new Category({ name: catName });
          return await newCategory.save();
        }
      });
  
      const resolvedCategories = await Promise.all(categoryPromises);
  
      // 2. Create a new post with the resolved categories
      const newPost = new Post({
        ...postData,
        categories: resolvedCategories.map((cat) => cat._id),
      });
  
      const savedPost = await newPost.save();
      res.status(200).json(savedPost);
    } catch (err) {
      res.status(500).json(err);
    }
  });
  

//update post
router.put("/:id", async (req,res) => {
    try {
        const post = await Post.findById(req.params.id);
        if(post.username === req.body.username){
            try {
                const updatedPost = await Post.findByIdAndUpdate(
                    req.params.id,
                    {
                        $set: req.body,
                    },
                    {
                        new: true
                    }
                );
                res.status(200).json(updatedPost);
            } catch (error) {
                res.status(500).json(error);
            }
        }else {
            res.status(401).json("You can update only your post.");
        }
    } catch (error) {
        res.status(500).json(error);
    }
});

//Delete post
router.delete("/:id", async (req,res) => {
    try {
        const post = await Post.findById(req.params.id);
        if(post.username === req.body.username){
            try{
                await post.delete();
                res.status(200).json("Post has been deleted...");
            } catch (error) {
                res.status(500).json(error);
            }
        }else {
            res.status(401).json("You can delete only your post.");
        }
    } catch (error) {
        res.status(500).json(error);
    }
});

//GET post
router.get("/:id", async (req, res) => {
    try {
        const post = await Post.findById(req.params.id);
        res.status(200).json(post);
    } catch (error) {
        res.status(500).json(error);
    }
})

//Get all posts
router.get("/", async (req, res) => {
    const username = req.query.user;
    const catName = req.query.cat;

    try {
        let posts;
        if(username){
            posts = await Post.find({ username });
        } else if(catName){
            posts = await Post.find({
                categories:{
                    $in: [catName],
                },
            });
        } else {
            posts = await Post.find();
            
        }
        res.status(200).json(posts);
    } catch (error) {
        res.status(500).json(error);
    }
})


module.exports = router;

Category模型代码

const mongoose = require("mongoose");

const CategorySchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
);

module.exports = mongoose.model("Category", CategorySchema);

排查与解决思路

1. 验证数据传输链路

  • 前端在handleSubmit中添加console.log(newPost.categories),确认提交的分类数组是否为非空字符串数组,有没有空值或格式错误。
  • 后端在创建post的接口开头添加console.log(req.body.categories),检查是否正确接收到前端传递的分类数据。如果没收到,确认express是否配置了json解析中间件:在app.js中添加app.use(express.json())。

2. 修复分类创建逻辑

  • 添加唯一约束:给Category模型的name字段添加unique: true,避免重复创建同名分类,同时能快速定位重复创建的错误:
    const CategorySchema = new mongoose.Schema(
      {
        name: {
          type: String,
          required: true,
          unique: true
        },
      },
      { timestamps: true }
    );
    
  • 处理大小写匹配:前端输入的分类名可能存在大小写差异(如"JavaScript"和"javascript"),导致重复创建。可以修改查询逻辑忽略大小写:
    const existingCategory = await Category.findOne({ 
      name: { $regex: new RegExp(`^${catName}$`, 'i') } 
    });
    
  • 错误捕获与处理:给分类创建的Promise添加错误捕获,避免单个分类创建失败导致整个请求失败:
    const categoryPromises = categories.map(async (catName) => {
      try {
        const existingCategory = await Category.findOne({ name: catName });
        if (existingCategory) {
          return existingCategory;
        } else {
          const newCategory = new Category({ name: catName });
          return await newCategory.save();
        }
      } catch (err) {
        // 处理重复键错误,返回已存在的分类
        if (err.code === 11000) {
          return await Category.findOne({ name: catName });
        }
        throw err;
      }
    });
    

3. 检查数据库与模型配置

  • 确认Category模型的文件路径是否正确,mongoose是否成功连接到MongoDB。
  • 查看MongoDB日志,检查是否有写入权限不足、集合创建失败等问题。

4. 前端输入逻辑优化

  • 检查putCatHandler函数,确认用户输入的分类是否被正确过滤:比如用户输入多个空格后按空格,是否会被if(!value.trim()) return正确拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 16:47:00