如何通过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}!`); });
问题原因及解决方案
核心问题
- 字段不匹配:表单提交的
date、time等字段直接在请求体根层级,但模型中这些字段属于run或weightTraining子文档。直接push(req.body)会导致workoutSchema的run/weightTraining字段未被赋值,最终存入null。 - 模型结构与期望不符:你期望
run/weightTraining是数组,但当前模型中它们是单个子文档类型。 - 路由逻辑错误:第二个路由中存在拼写错误(
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
相关产品推荐
相关产品推荐

