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

如何通过router.post与push()将HTML表单数据存入MongoDB子文档?

健身日志MEN应用子文档数据为null问题解决

问题描述

我正在开发一款健身日志MEN CRUD应用,目标是通过router.post结合push()方法,将HTML表单中的内容填充至MongoDB的子文档中。用户添加锻炼记录时可选择「跑步」或「力量训练」,选择后进入对应表单填写日期、时间、距离等相关数据。目前能成功创建锻炼条目,但存入的子文档数据均为null,尝试过调整路由、修改表单提交路径等方法,问题仍未解决。

模型代码

const mongoose = require('mongoose');

const RunSchema = new mongoose.Schema({
  date: {
    type: Number,
    required: true,
  },
  time: Number,
  distance: {
    type: Number,
    required: true,
  },
});

const WeightliftingSchema = new mongoose.Schema({
    date: {
        type: Number,
        required: true,
      },
      time: Number,
      exercise: {
          type: String,
          required: true,
      },
      notes: String,
      });
      
const workoutSchema =  new mongoose.Schema({
  run: RunSchema,
  weightTraining: WeightliftingSchema,
});

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
  },
  password: {
    type: String,
    required: true,
  },
  workouts: [workoutSchema],
});

const User = mongoose.model('User', userSchema);

module.exports = User;

路由代码

router.get('/new', async (req, res) => {
    res.render('workouts/new.ejs')
})

router.get('/new/run', async (req, res) => {
    res.render('workouts/new/run.ejs')
})

router.get('/new/weightlifting', async (req, res) => {
    res.render('workouts/new/weightlifting.ejs')
})

router.post('/', async (req, res) => {
    try {
        const currentUser = await User.findById(req.session.user._id);
        currentUser.workouts.push(req.body)
        await currentUser.save();
        res.redirect(`/users/${currentUser._id}/workouts`)

    } catch (error) {
        console.log(error)
        res.redirect('/')
    }
})

router.post('/:workoutId', async (req, res) => {
    try {
        const currentUser = await User.findById(req.session.user._id);
        const workout = currentUser.workouts.id(req.params.workoutId);
        currentUser.workout.run.push(req.body)
        res.redirect('workouts/show.ejs', { workout: workout })
    } catch (error) {
        console.log(error)
        res.redirect('/')
    }
})

HTML表单代码

<form action="/users/<%= user._id %>/workouts" method="POST">
    <label for="date">date:</label>
    <input type="number" id="date" name="date"/>
    
    <label for="time">time:</label>
    <input type="number" id="time" name="time"/>
    
    <label for="distance">distance:</label>
    <input type="number" id="distance" name="distance"/>
    
    <button type="submit">Add Item</button>
</form>

期望的数据结构

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
  },
  password: {
    type: String,
    required: true,
  },
  workouts: [
    run: [
     date: DATA FROM FORM
     time: DATA FROM FORM
     distance: DATA FROM FORM
   ]
    weightTraining: [null]
  ],
});

补充server.js代码

dotenv.config();
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const methodOverride = require('method-override');
const morgan = require('morgan');
const session = require('express-session');


/* ------------------------------- CONTROLLERS ------------------------------ */
const authController = require('./controllers/auth.js');
const workoutsController = require('./controllers/workouts.js');
const passUserToView = require('./middleware/pass-user-to-view.js');
const isSignedIn = require('./middleware/is-signed-in.js');

const port = process.env.PORT ? process.env.PORT : '3000';

mongoose.connect(process.env.MONGODB_URI);

mongoose.connection.on('connected', () => {
  console.log(`Connected to MongoDB ${mongoose.connection.name}.`);
});

app.use(express.urlencoded({ extended: false }));
app.use(methodOverride('_method'));
// app.use(morgan('dev'));
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: true,
  })
);

/* --------------------------------- ROUTES --------------------------------- */
app.get('/', (req, res) => {
  res.render('index.ejs', {
    user: req.session.user,
  });
});

app.use(passUserToView);
app.use('/auth', authController);
app.use(isSignedIn);
app.use('/users/:userId/workouts', workoutsController);

app.listen(port, () => {
  console.log(`The express app is ready on port ${port}!`);
});

问题原因及解决方案

核心问题

  1. 字段不匹配:表单提交的date、time等字段直接在请求体根层级,但模型中这些字段属于run或weightTraining子文档。直接push(req.body)会导致workoutSchema的run/weightTraining字段未被赋值,最终存入null。
  2. 模型结构与期望不符:你期望run/weightTraining是数组,但当前模型中它们是单个子文档类型。
  3. 路由逻辑错误:第二个路由中存在拼写错误(currentUser.workout应为currentUser.workouts),且逻辑不符合添加锻炼记录的需求。

分步修复

1. 调整模型结构(匹配期望)

如果期望每个workout条目下可以包含多条跑步/力量训练记录,修改workoutSchema为数组类型:

const workoutSchema = new mongoose.Schema({
  runs: [RunSchema],    // 改为数组存储多条跑步记录
  weightTrainings: [WeightliftingSchema]  // 改为数组存储多条力量训练记录
});

如果每个workout条目仅对应单次锻炼(要么跑步要么力量),则可以添加type字段区分类型:

const workoutSchema = new mongoose.Schema({
  type: { 
    type: String, 
    enum: ['run', 'weightlifting'], 
    required: true 
  },
  run: RunSchema,
  weightTraining: WeightliftingSchema,
});

2. 修改路由,包装请求体数据

针对跑步和力量训练分别创建专属POST路由,将表单数据包装到对应子文档字段中:

// 处理跑步记录提交
router.post('/run', async (req, res) => {
  try {
    const currentUser = await User.findById(req.session.user._id);
    // 将表单数据包装到run字段下
    const newWorkout = { runs: [req.body] }; // 对应数组类型模型
    // 如果是单条类型模型,用:const newWorkout = { type: 'run', run: req.body };
    currentUser.workouts.push(newWorkout);
    await currentUser.save();
    res.redirect(`/users/${currentUser._id}/workouts`);
  } catch (error) {
    console.log(error);
    res.redirect('/');
  }
});

// 处理力量训练记录提交
router.post('/weightlifting', async (req, res) => {
  try {
    const currentUser = await User.findById(req.session.user._id);
    const newWorkout = { weightTrainings: [req.body] }; // 对应数组类型模型
    // 如果是单条类型模型,用:const newWorkout = { type: 'weightlifting', weightTraining: req.body };
    currentUser.workouts.push(newWorkout);
    await currentUser.save();
    res.redirect(`/users/${currentUser._id}/workouts`);
  } catch (error) {
    console.log(error);
    res.redirect('/');
  }
});

3. 更新表单提交路径

修改跑步表单的action属性,指向专属路由:

<form action="/users/<%= user._id %>/workouts/run" method="POST">
    <!-- 原有表单字段不变 -->
</form>

力量训练表单同理,修改action为/users/<%= user._id %>/workouts/weightlifting。

4. 修复错误路由

删除或重构原有的router.post('/:workoutId')路由,如果需要给已有workout添加更多记录,可改为:

router.post('/:workoutId/run', async (req, res) => {
  try {
    const currentUser = await User.findById(req.session.user._id);
    const workout = currentUser.workouts.id(req.params.workoutId);
    workout.runs.push(req.body); // 向已有workout的runs数组添加新记录
    await currentUser.save();
    res.redirect(`/users/${currentUser._id}/workouts/${req.params.workoutId}`);
  } catch (error) {
    console.log(error);
    res.redirect('/');
  }
});

验证

完成修改后,提交表单时数据会被正确包装到run/weightTraining子文档中,存入MongoDB的内容将与期望结构一致,不再出现null值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:35:57