AlyaKra
AlyaKra

Reputation: 566

Mongoose : populate after save

I have a project where I'm using the MERN stack along with Redux.

When I'm fetching normal data with a get request, everything works perfectly after a clean page refresh because I can populate the query with referenced data, but when I'm saving for example new data, in the response json I'm only receiving the raw data of ObjectID and not the referenced data with it.

I believe I will need to populate the saved response with a new query or something similar to .populate in order to get the referenced data but I have no idea to do so.

I already tried something I've read here such as execPopulate on save() but didn't work.

Example of response JSON with post request :

{
  "suggestion": "61cb83b1e7a2d3469d2adc9e",
  "user": "61b4a9253f40b9c22969d11f",
  "content": "testing content",
  "rating": 3,
  "spoiler": false,
  "_id": "61d7df8c5883e282879c6fb9",
  "date": "2022-01-07T06:37:00.196Z",
  "upvotes": [],
  "__v": 0
}

Example of response with get request that I would want to receive in save too, notice the fields suggestion & user are populated :

 {
    "_id": "61d7df8c5883e282879c6fb9",
    "suggestion": {
      "_id": "61cb83b1e7a2d3469d2adc9e",
      "title": "Beyond",
      "suggestion_type": "Book",
      "image": "91b2d138-883a-4fab-8e0c-a554dd466349.png",
      "token": "LSSRuE"
    },
    "user": {
      "_id": "61b4a9253f40b9c22969d11f",
      "username": "test_user",
      "avatar": "https://i.pinimg.com/2349087qsbjkqsbd.jpg"
    },
    "content": "Testing content",
    "rating": 3,
    "spoiler": false,
    "date": "2022-01-07T06:37:00.196Z",
    "__v": 0
  }

Save route :

router.post(
  '/:token/reviews',
  [auth, [check('rating', 'Rating is required').not().isEmpty()]],
  async (req, res) => {
       
    try {
      const suggestion = await Suggestion.findOne({ token: req.params.token });
       
      const review = await Review.find({
        suggestion: suggestion.id,
      });          

      const newReview = new Review({
        content: req.body.content,
        rating: req.body.rating,
        spoiler: req.body.spoiler,
        user: req.user.id,
        suggestion: suggestion.id,
      });

      await newReview.save();

      res.json(newReview);
    } catch (error) {
      console.error(error.message);
      res.status(500).send('Server Error');
    }
  }
);

Get route :

router.get('/:token/reviews/', auth, async (req, res) => {
  try {
    const suggestion = await Suggestion.findOne({
      token: req.params.token,
    });
    const review = await Review.find({
      suggestion: suggestion.id,
    })
      .populate('user', ['avatar', 'username', 'id'])
      .populate('suggestion', [
        'id',
        'token',
        'title',
        'image',
        'suggestion_type',
      ]);

    if (!review) {
      res
        .status(404)
        .json({ msg: 'No reviews found for the specified suggestion' });
    }

    res.json(review);
  } catch (error) {
    console.error(error.message);
    if (error.kind === 'ObjectId') {
      res.status(404).json({ msg: 'Review not found' });
    }
    res.status(500).send('Server Error');
  }
});

Reviews model :

const mongoose = require('mongoose');

const ReviewSchema = new mongoose.Schema({
  suggestion: { type: mongoose.Schema.Types.ObjectId, ref: 'suggestion' },

  user: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },

  date: { type: Date, default: Date.now },

  content: {
    type: String,
  },
  rating: {
    type: Number,
  },
  spoiler: { type: Boolean, default: 0 },

});

module.exports = mongoose.model('review', ReviewSchema);

Upvotes: 2

Views: 3826

Answers (2)

InaNutshell
InaNutshell

Reputation: 1

You can do this by chaining populate after findOne using dot notation.
ex.

const person = await Person.findOne({
  name: 'Ian Fleming'
}).populate('stories');

bonus: for field selection, you can use mongo syntax.
ex.

populate('stories', { title: 1, author: 1 });

will return an object populated with those fields only including _id.
Refer to mongodb docs for more

Upvotes: 0

AlyaKra
AlyaKra

Reputation: 566

Solved.

I added this line await newReview.populate('user'); after await newReview.save();

From the official documentation : https://mongoosejs.com/docs/populate.html#populate_an_existing_mongoose_document

Populating an existing document

If you have an existing mongoose document and want to populate some of its paths, you can use the Document#populate() method.

const person = await Person.findOne({ name: 'Ian Fleming' });

person.populated('stories'); // null

// Call the `populate()` method on a document to populate a path.
await person.populate('stories');

person.populated('stories'); // Array of ObjectIds
person.stories[0].name; // 'Casino Royale'

Upvotes: 5

Related Questions