I will like to return the previous object and the next object of a matching object id in node

Viewed 54

Please i am new to node js and MongoDB. When i want to retrieve a post by id, i want to be able to retrieve the previous post and next post also. this is my post, it only retrieves the current post by id.

Post.findById(req.params.postId)
    .then((existingpost) => {
        console.log(Post.find(req.params.postId))
      if (existingpost) {
        res.send(existingpost);
      }
      return res.status(404).send({
        message: "Post does not exist with id " + req.params.postId,
      });
    })
    .catch((err) => {
      if (err.kind === "ObjectId") {
        return res.status(404).send({
          message: "Post does not exist with id " + req.params.postId,
        });
      }
      return res.status(500).send({
        message:
          "Some error occurred while retrieving the post with postId " +
          req.params.postId,
      });
    });
};

I currently receive the object with the id like this which is fine.

{
    "_id": "6009f3e294d8a033402a76e7",
    "title": "Covid 19 in Italy",
    "author": "John Doe",
    "createdAt": "2021-01-21T21:36:34.514Z",
    "updatedAt": "2021-01-21T21:36:34.514Z",
    "__v": 0
}

But i will love to receive the object of the current id, the previous object and the next object. something like this.

[{
    "_id": "3230g5e382d8a033402a76e7",
    "title": "Effect of Covid on the Economy",
    "author": "John Doe",
    "createdAt": "2021-01-21T21:36:34.514Z",
    "updatedAt": "2021-01-21T21:36:34.514Z",
    "__v": 0
},
{
    "_id": "6009f3e294d8a033402a76e7",
    "title": "Covid 19 in Italy",
    "author": "John Doe",
    "createdAt": "2021-01-21T21:36:34.514Z",
    "updatedAt": "2021-01-21T21:36:34.514Z",
    "__v": 0
},
{
    "_id": "4567hye294d8a033402a76e7",
    "title": "Life after Covid",
    "author": "John Doe",
    "createdAt": "2021-01-21T21:36:34.514Z",
    "updatedAt": "2021-01-21T21:36:34.514Z",
    "__v": 0
}]
2 Answers

I am not sure is there any straight way to do this, you can try aggregation,

Using UUID and CreatedAt:

  • $facet to get all documents in all after sorting in ascending order by createdAt
  • $let to define vars states with start and total documents,
  • $cond check condition if index of input uuid is zero then return start: 0 and total: 2 documents we have to slice from all array, else get current index and subtract minus 1 and total: 3
  • in to return slice documents on the base of start and total
Post.aggregate([
  { $facet: { all: [{ $sort: { createdAt: 1 } }] } },
  {
    $project: {
      result: {
        $let: {
          vars: {
            states: {
              $cond: [
                { $eq: [{ $indexOfArray: ["$all._id", req.params.postId] }, 0] },
                { start: 0, total: 2 },
                {
                  start: {
                    $subtract: [{ $indexOfArray: ["$all._id", req.params.postId] }, 1]
                  },
                  total: 3
                }
              ]
            }
          },
          in: { $slice: ["$all", "$$states.start", "$$states.total"] }
        }
      }
    }
  }
])

Playground


Using ObjectID:

  • convert your string input id req.params.postId to object id using mongoose.Types.ObjectId
  • $facet to separate result,
  • first, $match to get current and next documents, $sort _id in descending order, $limit 2
  • second, $match to get previous document, $sort _id in descending order, $limit 1
  • $project to get result after concat both array first and second using $concatArrays
req.params.postId = mongoose.Types.ObjectId(req.params.postId);
Post.aggregate([
  {
    $facet: {
      first: [
        { $match: { _id: { $gte: req.params.postId } } },
        { $sort: { _id: 1 } },
        { $limit: 2 }
      ],
      second: [
        { $match: { _id: { $lt: req.params.postId } } },
        { $sort: { _id: -1 } },
        { $limit: 1 }
      ]
    }
  },
  { $project: { result: { $concatArrays: ["$first", "$second"] } } }
])

Playground

Since its a UUID, this approach might help you..

  • $sort to sort the documents by asc
  • $group and $unwind to get the index
  • $facet to categorize the incoming data into current and allDocs
  • We know current is only one object, so we do $unwind to deconstruct the array
  • We already know the index, so we use $filter to get prev, current and next using index
  • $unwind to deconstruct the array
  • $replaceRoot to make the objects to the root

Here is the script

db.collection.aggregate([
   $sort: { createdAt: 1 } },
  {
    $group: {
      _id: null,
      data: { $push: "$$ROOT"}
    }
  },
  { $unwind: { path: "$data", includeArrayIndex: "index" } },
  {
    $facet: {
      current: [
        { $match: { "data._id": "3230g5e382d8a033402a76e7" } }
      ],
      allDocs: [
        { $match: {} }
      ]
    }
  },
  {
    $unwind: "$current"
  },
  {
    $project: {
      docs: {
        $filter: {
          input: "$allDocs",
          cond: {
            $or: [
              { $eq: [ "$$this.index", { $subtract: [ "$current.index", 1 ] } ] },
              { $eq: [ "$$this.index", "$current.index" ] },
              { $eq: [ "$$this.index", { $add: [ "$current.index", 1 ] } ] }
            ]
          }
        }
      }
    }
  },
  { "$unwind": "$docs" },
  { "$replaceRoot": { "newRoot": "$docs.data" } }
])

Working Mongo playground

There are many ways to do this, this is one of the way. If you feel you have a lot of document, then try to avoid $unwind which is expensive, in that case you can try using createdDate instead of index

Related