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

Mongoose子文档返回undefined问题排查与解决

问题分析与解决:Express+Mongoose中子文档reviews返回undefined问题

问题现象

基于《Getting MEAN》搭建的Express应用,调用GET /api/locations/:locationid/reviews/:reviewid接口时,已成功查询到目标location文档,但location.reviews返回undefined,导致if(location.reviews && location.reviews.length>0)判断失败,接口返回错误信息。

相关代码与输出

MongoDB文档

{
  "_id": { "$oid": "65537eb149dd64103a4b7950" },
  "name": "Starcups",
  "address": "125 High Street, Reading, RG6 1PS",
  "rating": 3,
  "facilities": ["Hot drinks", "Food", "Premium wifi"],
  "coords": [-0.9690884, 51.455041],
  "openingTimes": [
    { "days": "Monday - Friday", "opening": "7:00am", "closing": "7:00pm", "closed": false },
    { "days": "Saturday", "opening": "8:00am", "closing": "5:00pm", "closed": false },
    { "days": "Sunday", "closed": true }
  ],
  "reviews": [
    {
      "author": "Lekan Yusuf",
      "id": { "$oid": "6560c0dfadc556388f6b8e93" },
      "rating": 4,
      "timestamp": { "$date": "1992-06-11T23:00:00Z" },
      "reviewText": "Good wifi, moderate coffee"
    },
    {
      "author": "Lekan Yusuf",
      "id": { "$oid": "6563c6c0d8b148207dcc9817" },
      "rating": 4,
      "timestamp": { "$date": "1992-06-11T23:00:00Z" },
      "reviewText": "Good wifi, moderate coffee"
    }
  ]
}

路由配置

var express = require('express');
var router = express.Router();
var ctrlReviews =require('../controllers/reviews');

//reviews routes
router.get('/locations/:locationid/reviews/:reviewid', ctrlReviews.reviewsReadOne);
router.post('locations/:locationid/reviews', ctrlReviews.createReview);
router.put('locations/:locationid/reviews/reviewid', ctrlReviews.reviewsUpdateOne);
router.delete('locations/:locationid/reviews/reviewid', ctrlReviews.reviewsDeleteOne);

控制器核心代码

module.exports.reviewsReadOne = function(req, res){
    console.log('in ReviewsReadOne');
        
    if(req.params && req.params.locationid && req.params.reviewid){
        console.log('locationid: '+ req.query.locationid + ' reviewid: '+ req.query.reviewid);
        Loc.findById(req.query.locationid, 'name reviews').exec(
            function(err,location){
                var response, review;
                if(!location){
                    sendJsonResponse(res, 404, {"message": "locationid not found"});
                    return;   
                } else if (err){
                    sendJsonResponse(res, 404, err);
                    return;
                }
                console.log('location just before check:\n' + location);
                console.log('\n reviews: '+ location.reviews);
                
                if(location.reviews && location.reviews.length >0){
                    review = location.reviews.id(req.params.reviewid);
                    if(!review){
                        sendJsonResponse(res, 400, {"message":"Review id not found"});
                    } else {
                        response ={
                            location:{ name: location.name, id: req.query.id },
                            review:review
                        };
                        sendJsonResponse(res, 200, response);
                    }
                } else {
                    sendJsonResponse(res, 400, {message:"Not found, locationid and reviewid are both required"});
                }
            }
        );
    } else{
        sendJsonResponse(res, 400, {"message": "No location id and or review id"});
        return;
    }
}

终端输出

in ReviewsReadOne
locationid: 65538583fc3b3ac2c0b83a8d reviewid: 6563c74141216ec9d9ec7c38
location just before check:
{
  _id: new ObjectId("65538583fc3b3ac2c0b83a8d"),
  name: 'Starfi',
  reviews: [
    {
      author: 'Subomi ogunderu',
      id: new ObjectId("6563c74141216ec9d9ec7c38"),
      rating: 3,
      timestamp: 1995-08-11T23:00:00.000Z,
      reviewText: 'Excellent wifi and the food is great'
    }
  ]
}

 reviews: undefined

GET /api/locations/locationid/reviews/reviewid?locationid=65538583fc3b3ac2c0b83a8d&reviewid=6563c74141216ec9d9ec7c38 400 566.482 ms - 66

问题原因

  1. 路径参数与查询参数混用
    路由定义的是路径参数/:locationid/:reviewid,但请求时错误使用查询参数?locationid=xxx&reviewid=yyy,同时控制器中混用req.query(查询参数)和req.params(路径参数)获取ID,导致逻辑混乱。虽然偶然查到了location文档,但Mongoose Document对象的属性访问规则受到影响。

  2. Mongoose Document属性访问限制
    打印location时显示的是文档的JSON序列化结果,但location本身是Mongoose的Document实例,若Schema未正确定义reviews为子文档数组,直接访问location.reviews会返回undefined。

  3. 路由配置错误
    POST/PUT/DELETE路由缺少开头的/,且PUT/DELETE的reviewid未定义为路径参数,会导致路由无法匹配。

解决方案

1. 修正请求URL格式

按照路由定义,使用路径参数传递ID,正确请求URL示例:

GET /api/locations/65538583fc3b3ac2c0b83a8d/reviews/6563c74141216ec9d9ec7c38

2. 修正控制器参数逻辑

统一使用req.params获取路径参数,同时可将Mongoose Document转为普通JS对象避免属性访问问题:

module.exports.reviewsReadOne = function(req, res){
    console.log('in ReviewsReadOne');
        
    if(req.params && req.params.locationid && req.params.reviewid){
        console.log('locationid: '+ req.params.locationid + ' reviewid: '+ req.params.reviewid);
        Loc.findById(req.params.locationid, 'name reviews').exec(
            function(err,location){
                var response, review;
                if(!location){
                    sendJsonResponse(res, 404, {"message": "locationid not found"});
                    return;   
                } else if (err){
                    sendJsonResponse(res, 404, err);
                    return;
                }
                // 转为普通JS对象,避免Mongoose Document属性访问限制
                const locationObj = location.toObject();
                console.log('location just before check:\n', locationObj);
                console.log('\n reviews: ', locationObj.reviews);
                
                if(locationObj.reviews && locationObj.reviews.length >0){
                    // 子文档数组的id方法需要使用原Document对象
                    review = location.reviews.id(req.params.reviewid);
                    
                    if(!review){
                        sendJsonResponse(res, 400, {"message":"Review id not found"});
                    } else {
                        response ={
                            location:{
                                name: location.name,
                                id: location._id // 使用文档自身的_id,而非req.query.id
                            },
                            review: review.toObject() // 转为普通对象返回
                        };
                        sendJsonResponse(res, 200, response);
                    }
                } else {
                    sendJsonResponse(res, 400, {message:"No reviews found for this location"});
                }
            }
        );
    } else{
        sendJsonResponse(res, 400, {"message": "No location id and or review id provided in URL path"});
        return;
    }
}

3. 确保Location Schema正确定义子文档数组

检查并修正Location模型的Schema,确保reviews被定义为子文档数组:

const mongoose = require('mongoose');

const reviewSchema = new mongoose.Schema({
    author: String,
    rating: Number,
    timestamp: Date,
    reviewText: String
});

const locationSchema = new mongoose.Schema({
    name: String,
    address: String,
    rating: Number,
    facilities: [String],
    coords: [Number],
    openingTimes: [{
        days: String,
        opening: String,
        closing: String,
        closed: Boolean
    }],
    reviews: [reviewSchema] // 必须明确定义为子文档数组
});

module.exports = mongoose.model('Loc', locationSchema);

4. 修正其他路由的路径错误

补充开头的/,并将PUT/DELETE的reviewid定义为路径参数:

//reviews routes
router.get('/locations/:locationid/reviews/:reviewid', ctrlReviews.reviewsReadOne);
router.post('/locations/:locationid/reviews', ctrlReviews.createReview);
router.put('/locations/:locationid/reviews/:reviewid', ctrlReviews.reviewsUpdateOne);
router.delete('/locations/:locationid/reviews/:reviewid', ctrlReviews.reviewsDeleteOne);

验证效果

修改完成后,使用正确的路径参数请求接口,终端中location.reviews将正常返回数组,接口会返回对应的review数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 15:08:13