【问题标题】:Routing to sub docs with express 4 and mongoose使用 express 4 和 mongoose 路由到子文档
【发布时间】:2018-04-01 02:56:21
【问题描述】:

编辑:问题可能是路径问题。我当前的查询如下所示:

router.route('/projects/:project_id/techDetails')
  .get(function(req, res) {
    Project.findById(req.params.project_Id, function(err, project) {
        if (err)
            return res.send(err);
        res.json(project);
        console.log('get success (project techDetails)');
    });
});

这将返回null。尽管除了在路由中添加了 `/techDetails' 之外,它在各方面都与工作代码行相同。

原始问题:

我正在使用 express 和 mongo 构建一个 MEAN 堆栈应用程序。我不知道如何正确路由到嵌套文档。

这是我的项目架构

const ProjectSchema = new Schema({
  idnumber: { type: Number, required: true },
  customername: String,
  projectdetails: String,
  jobaddress: String,
  techDetails: [{
    scope: String,
    edgedetail: String,
    lamination: String,
    stonecolour: String,
    slabnumber: String,
    slabsupplier: String,
    purchaseordernum: String,
    splashbacks: String,
    apron: String,
    hotplate: String,
    sink: String,
    sinkdetails: String,
    tappos: String
  }],
  sitecontactname: String,
  sitecontactnum: String,
  specialreq: String,
  install_date: String,
  created_on: { type: Date, default: Date.now },
  created_by: { type: String, default: 'SYSTEM' },
  active: { type: Boolean, default: true },
  flagged: { type: Boolean, default: false },
});

我可以通过GETPOST 成功路由到/projects,以及通过GETPUTDEL 路由到/projects/:project_id

使用PUT 路由和项目的_ID,我可以将新条目推送到项目的techDetails 子文档数组。生成的 JSON 数据 如下所示:

{
"_id": "59e577e011a3f512b482ef13",
"idnumber": 52,
"install_date": "10/20/2017",
"specialreq": "some...",
"sitecontactnum": "987654321",
"sitecontactname": "bill",
"jobaddress": "123 st",
"projectdetails": "some stuff",
"customername": "B Builders",
"__v": 16,
"flagged": false,
"active": true,
"created_by": "SYSTEM",
"created_on": "2017-10-17T03:24:16.423Z",
"techDetails": [
    {
        "scope": "Howitzer",
        "edgedetail": "12mm",
        "lamination": "No",
        "stonecolour": "Urban™",
        "slabnumber": "1",
        "slabsupplier": "Caesarstone",
        "purchaseordernum": "no",
        "splashbacks": "No",
        "apron": "No",
        "hotplate": "N/A",
        "sink": "N/A",
        "sinkdetails": "no",
        "tappos": "no",
        "_id": "59e577e011a3f512b482ef14"
    },
    {
        "scope": "kitchen",
        "edgedetail": "12mm",
        "lamination": "etc",
        "_id": "59e7da445d9d7e109c18f38b"
    },
    {
        "scope": "Vanity",
        "edgedetail": "12mm",
        "lamination": "No",
        "stonecolour": "Linen™",
        "slabnumber": "1",
        "slabsupplier": "Caesarstone",
        "purchaseordernum": "1",
        "splashbacks": "No",
        "apron": "No",
        "hotplate": "N/A",
        "sink": "N/A",
        "sinkdetails": "no",
        "tappos": "woo",
        "_id": "59e81e3324fb750fb46f8248"
    }//, more entries omitted for brevity
  ]
}

如您所见,到目前为止一切都按预期工作。但是现在我需要编辑和删除这个 techDetails 数组中的单个条目。我还想直接使用projects/:project_id/techDetailsprojects/:project_id/techDetails/:techdetails_id 路由到他们。

据我所知,有两种方法。我可以:

A) 为使用 mergeParams 的 techDetails 使用新的路由文件。这是我目前正在尝试的方法,但是我不知道如何完成.find 以返回所有techDetails,因为我只能使用Project 模型架构并且我不确定如何访问子文档。

摘自我的 routes.js

const techDetails = require('./techDetails');
//other routes here

//see techdetails file
router.use('/projects/:project_id/techdetails', techDetails);

//here lies an earlier, failed attempt
/* router.route('/projects/:project_id/techdetails/:techDetails_id')
.get(function(req, res) {
    Project.findById(req.params.project_id.techDetails_id, function(err, 
project) {
        if (err)
            return res.send(err);
        res.json(project.techDetails);
        console.log('get success (techDetails)');
    });
  })
; */

还有我的 techdetails.js

const express = require('express');
const Project = require('./models/project');
const router = express.Router({mergeParams: true});

router.get('/', function (req, res, next) {
/*  Project.find(function(err, techDetails) {
    if (err)
        return res.send(err);
    res.json(techDetails);
    console.log('get success (all items)');
  }); */
  res.send('itemroutes ' + req.params);
})

router.get('/:techDetails_id', function (req, res, next) {
  res.send('itemroutes ' + req.params._id)
})

module.exports = router

我可以成功地检查路由是否与 Postman 一起使用,两者都会收到响应。现在的问题是,我想使用 res.jsonProject.find(或类似的)来获取 techDetails,而不是 res.send

不过还有另一种选择:

B) 将 techDetails 文档放入它自己的架构中,然后在项目中填充 ID 数组。

但是这似乎更复杂,所以如果可以的话,我宁愿避免这样做。

欢迎任何想法和建议。如果需要更多代码,请告诉我。

【问题讨论】:

    标签: node.js mongodb express mongoose mean-stack


    【解决方案1】:

    在这种特殊情况下,我会将 techDetails 放在单独的架构中:

    const ProjectSchema = new Schema({
      idnumber: { type: Number, required: true },
      customername: String,
      projectdetails: String,
      jobaddress: String,
      techDetails: [techDetailsSchema],
      sitecontactname: String,
      sitecontactnum: String,
      specialreq: String,
      install_date: String,
      created_on: { type: Date, default: Date.now },
      created_by: { type: String, default: 'SYSTEM' },
      active: { type: Boolean, default: true },
      flagged: { type: Boolean, default: false },
    });
    

    不要使用mongoose.model 注册 techDetails 架构,因为它是一个子文档。将它放在一个单独的文件中,并在项目模型文件(const techDetailsSchema = require('./techDetails.model');)中要求它。

    我会像这样创建控制器函数:

    使用 GET(全部):

    module.exports.techDetailsGetAll = function (req, res) {   
      const projectId = req.params.projectId;
    
      Project
        .findById(projectId)
        .select('techDetails')
        .exec(function (err, project) {
          let response = { };
    
          if (err) {
            response = responseDueToError(err);
          } else if (!project) {
            response = responseDueToNotFound();
          } else {
            response.status = HttpStatus.OK;
            response.message = project.techDetails;
          }
    
          res.status(response.status).json(response.message);
        });
    }
    

    使用 GET(一):

    module.exports.techDetailsGetOne = function (req, res) {
      const projectId = req.params.projectId;
      const techDetailId = req.params.techDetailId;
    
      Project
        .findById(projectId)
        .select('techDetails')
        .exec(function (err, project) {
          let response = { };
    
          if (err) {
            response = responseDueToError(err);
          } else if (!project) {
            response = responseDueToNotFound();
          } else {
            let techDetails = project.techDetails.id(techDetailId);
    
            if (techDetails === null) {
              response = responseDueToNotFound();
            } else {
              response.status = HttpStatus.OK;
              response.message = techDetails;
            }
          }
    
          res.status(response.status).json(response.message);
        });
    }
    

    使用 POST 添加:

    module.exports.techDetailsAddOne = function (req, res) {
      const projectId = req.params.projectId;
    
      let newTechDetails = getTechDetailsFromBody(req.body);
    
      Project
      .findByIdAndUpdate(projectId,
        { '$push': { 'techDetails': newTechDetails } },
        {
          'new': true,
          'runValidators': true
        },
        function (err, project) {
          let response = { };
    
          if (err) {
            response = responseDueToError(err);
          } else if (!project) {
            response = responseDueToNotFound();
          } else {
            response.status = HttpStatus.CREATED;
            response.message = project.techDetails;  // for example
          }
    
          res.status(response.status).json(response.message);
        });
    }
    

    用于使用 PUT 进行更新

    module.exports.techDetailsUpdateOne = function (req, res) {
      const projectId = req.params.projectId;
      const techDetailId = req.params.techDetailId;
    
      let theseTechDetails = getTechDetailsFromBody(req.body);
      theseTechDetails._id = techDetailId;  // can be skipped if body contains id
    
      Project.findOneAndUpdate(
        { '_id': projectId, 'techDetails._id': techDetailId },
        { '$set': { 'techDetails.$': theseTechDetails } },
        {
          'new': true,
          'runValidators': true
        },
        function (err, project) {
          let response = { };
    
          if (err) {
            response = responseDueToError(err);
            res.status(response.status).json(response.message);
          } else if (!project) {
            response = responseDueToNotFound();
            res.status(response.status).json(response.message);
          } else {
            project.save(function (err) {
              if (err) {
                response = responseDueToError(err);
              } else {
                response.status = HttpStatus.NO_CONTENT;
              }
    
              res.status(response.status).json(response.message);
            })
          }
        });
    }
    

    然后用 DELETE 删除:

    module.exports.techDetailsDeleteOne = function (req, res) {
      const projectId = req.params.projectId;
      const techDetailId = req.params.techDetailId;
    
      Project
        .findById(projectId)
        .select('techDetails')
        .exec(function (err, project) {
          let response = { }
    
          if (err) {
            response = responseDueToError(err);
            res.status(response.status).json(response.message);
          } else if (!project) {
            response = responseDueToNotFound();
            res.status(response.status).json(response.message);
          } else {
            let techDetail = project.techDetails.id(techDetailId);
    
            if (techDetail !== null) {
              project.techDetails.pull({ '_id': techDetailId });
    
              project.save(function (err) {
                if (err) {
                  response = responseDueToError(err);
                } else {
                  response.status = HttpStatus.NO_CONTENT;
                }
    
                res.status(response.status).json(response.message);
              })
            } else {
              response = responseDueToNotFound();
              res.status(response.status).json(response.message);
            }
          }
        });
    }
    

    最后像这样路由:

    router
        .route('/projects')
        .get(ctrlProjects.projectsGetAll)
        .post(ctrlProjects.projectsAddOne);
    
    router
        .route('/projects/:projectId')
        .get(ctrlProjects.projectsGetOne)
        .put(ctrlProjects.projectsUpdateOne)
        .delete(ctrlProjects.projectsDeleteOne);
    
    router
        .route('/projects/:projectId/techDetails')
        .get(ctrlTechDetails.techDetailsGetAll)
        .post(ctrlTechDetails.techDetailsAddOne);
    
    router
        .route('/projects/:projectId/techDetails/:techDetailId')
        .get(ctrlTechDetails.techDetailsGetOne)
        .put(ctrlTechDetails.techDetailsUpdateOne)
        .delete(ctrlTechDetails.techDetailsDeleteOne);
    

    当我独立于文档的其余部分不断更新子文档时,我更喜欢这种方式。它不会创建单独的集合,因此无需填充。

    编辑: This 答案更详细地说明了您应该使用嵌入还是引用。我的答案使用嵌入。

    【讨论】:

    • 所以您可以将子文档定义为模式而不是模型?谢谢,这使选项 B 更容易。我更喜欢避免使用单独的控制器,但随着我的程序规模越来越大,开始使用它们可能是个好主意。如果我能让它工作,我会告诉你的!感谢您的快速回答!
    • response.message = project.techDetails; 返回 null 而 techDetails 本身返回 undefined ...这与我之前遇到的问题几乎相同。我似乎无法访问任何嵌套的内容。
    • 使用 mongoose.model 注册模式会将 techDetails 放在一个单独的集合中(有关更多信息,请参阅here)。如果您计划在每个项目中拥有大量 techDetails,那么这可能就是您想要的,因为 techDetails 数组将使项目变得非常大。或者,如果您在项目之间分享 techDetails。否则我会选择没有单独的收藏。最大 BSON 文档大小为 16 MB。
    • 至于 project.techDetails 返回 null:你能在 exec(function(err, project) { 之后放一个 console.log(project) 看看它打印什么吗?您还可以暂时删除 select 语句以返回整个项目。如果您在将数据放入项目后添加了架构,那么完全删除集合然后再次添加数据可能会更容易。返回 null 意味着数据库没有找到任何子文档。当没有找到任何 techDetails 时,您可能希望使用 response.message = project.techDetails || []; 返回一个空数组。
    • 啊,可能是这样。不知道为什么我没有想到这一点,我敢肯定,当我进行更改时,我一定已经在脑海中考虑过了。我会尝试所有新数据。
    【解决方案2】:

    所以,我想到的解决方案是 A) 和 B) 的组合。我使用了一个单独的路由文件并将({mergeParams: true}) 放在路由器声明中,我为techDetails 嵌套模型创建了一个单独的文件,但没有声明它。但是我不相信这两个实际上有任何意义......但无论如何。

    我最终得到的工作代码是,在我的路线中:

    router.use('/projects/:project_id/techDetails', TechDetails);
    

    techDetails.js:

    const router = express.Router({mergeParams: true});
    
    router.route('/')
      .get(function(req, res) {
        Project.findById(req.params.project_id,
          'techDetails', function(err, project) {
            if (err)
                return res.send(err);
            res.json(project);
            console.log('get success (project techDetails)');
        });
    });
    

    它有什么不同?即Project.findById 行中的'techDetails', 参数。根据 mongoose API,这充当了一个选择语句。唯一的其他主要区别是我修复了原始代码中的错字(project_id 写成project_Id。可疑...)。如果我使用 VS 或其他东西而不是 notepad++,我可能会注意到这一点,但它是我首选的编码领域。

    可能返回res.json(project.techDetails) 并删除'techDetails', 选择参数,但我可能不会对此进行测试。

    编辑:techDetails 迁移到单独的文件意味着它们不再使用objectIds 生成,这对于PUT 和DEL 至关重要。我可能已经能够在数组声明中使用一对简单的花括号来解决它们,但直到我将它重新迁移回项目架构后我才想到这一点......

    【讨论】:

      猜你喜欢
      • 2016-06-14
      • 1970-01-01
      • 2021-05-16
      • 2015-10-04
      • 1970-01-01
      • 2015-06-02
      • 2018-10-25
      • 1970-01-01
      • 2013-06-29
      相关资源
      最近更新 更多