Perform $lookup with object value in array from pipeline

Viewed 391

So I have 3 models user, property, and testimonials.

Testimonials have a propertyId, message & userId. I've been able to get all the testimonials for each property with a pipeline.

Property.aggregate([
  { $match: { _id: ObjectId(propertyId) } },
  {
      $lookup: {
        from: 'propertytestimonials',
        let: { propPropertyId: '$_id' },
        pipeline: [
          {
            $match: {
              $expr: {
                $and: [{ $eq: ['$propertyId', '$$propPropertyId'] }],
              },
            },
          },
        ],
        as: 'testimonials',
      },
    },
]) 

The returned property looks like this

{
 .... other property info,
 testimonials: [
  {
    _id: '6124bbd2f8eacfa2ca662f35',
    userId: '6124bbd2f8eacfa2ca662f29',
    message: 'Amazing property',
    propertyId: '6124bbd2f8eacfa2ca662f2f',
  },
  {
    _id: '6124bbd2f8eacfa2ca662f35',
    userId: '6124bbd2f8eacfa2ca662f34',
    message: 'Worth the price',
    propertyId: '6124bbd2f8eacfa2ca662f2f',
  },
 ]
}

User schema

firstName: {
  type: String,
  required: true,
},
lastName: {
  type: String,
  required: true,
},
email: {
  type: String,
  required: true,
},

Property schema

name: {
  type: String,
  required: true,
},
price: {
  type: Number,
  required: true,
},
location: {
  type: String,
  required: true,
},

Testimonial schema

propertyId: {
  type: ObjectId,
  required: true,
},
userId: {
  type: ObjectId,
  required: true,
},
testimonial: {
  type: String,
  required: true,
},

Now the question is how do I $lookup the userId from each testimonial so as to show the user's info and not just the id in each testimonial?

I want my response structured like this

{
    _id: '6124bbd2f8eacfa2ca662f34',
    name: 'Maisonette',
    price: 100000,
    testimonials: [
        {
            _id: '6124bbd2f8eacfa2ca662f35',
            userId: '6124bbd2f8eacfa2ca662f29',
            testimonial: 'Amazing property',
            propertyId: '6124bbd2f8eacfa2ca662f34',
            user: {
                _id: '6124bbd2f8eacfa2ca662f29',
                firstName: 'John',
                lastName: 'Doe',
                email: 'jd@mail.com',
            }
        },
        {
            _id: '6124bbd2f8eacfa2ca662f35',
            userId: '6124bbd2f8eacfa2ca662f27',
            testimonial: 'Worth the price',
            propertyId: '6124bbd2f8eacfa2ca662f34',
            user: {
                _id: '6124bbd2f8eacfa2ca662f27',
                firstName: 'Sam',
                lastName: 'Ben',
                email: 'sb@mail.com',
            }
        }
    ]
}
1 Answers

You can put $lookup stage inside pipeline,

  • $lookup with users collection
  • $addFields, $arrayElemAt to get first element from above user lookup result
Property.aggregate([
  { $match: { _id: ObjectId(propertyId) } },
  {
    $lookup: {
      from: "propertytestimonials",
      let: { propPropertyId: "$_id" },
      pipeline: [
        {
          $match: {
            $expr: { $eq: ["$propertyId", "$$propPropertyId"] }
          }
        },
        {
          $lookup: {
            from: "users",
            localField: "userId",
            foreignField: "_id",
            as: "user"
          }
        },
        {
          $addFields: {
            user: { $arrayElemAt: ["$user", 0] }
          }
        }
      ],
      as: "testimonials"
    }
  }
])

Playground

Related