使用express 4和mongoose

时间:2017-10-20 00:07:26

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

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

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 },
});

我可以使用/projectsGET以及POST/projects/:project_idGETPUT成功路由到DEL

使用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.send我希望res.json使用Project.find(或类似)来获取techDetails

然而还有另一种选择:

B)将techDetails文档放入自己的模式中,然后在项目中填充一系列ID。

然而,这似乎更复杂,所以如果可以,我宁愿避免这样做。

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

2 个答案:

答案 0 :(得分:0)

在这种特殊情况下,我会将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 },
});

不要将techDetails架构注册为mongoose.model,因为它是一个子文档。将它放在一个单独的文件中,并在项目模型文件(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回答更详细。我的回答是使用嵌入。

答案 1 :(得分:0)

所以,我得到的解决方案是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)');
    });
});

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

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

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