1

So I have this user model which refers to blog

User Model

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const bcrypt = require("bcryptjs");

const userSchema = new Schema(
  {
    email: {
      type: String,
      required: true,
      index: {
        unique: true
      }
    },
    password: {
      type: String,
      required: true
    },
    name: {
      type: String,
      required: true
    },
    website: {
      type: String
    },
    bio: {
      type: String
    },
    blogs: [
      {
        type: Schema.Types.ObjectId,
        ref: "Blog"
      }
    ]
  },
  {
    timestamps: {
      createdAt: "created_at",
      updatedAt: "updated_at"
    }
  }
);


userSchema.pre("save", function(next) {
  const user = this;
  if (!user.isModified("password")) return next();

  bcrypt.genSalt(10, function(err, salt) {
    if (err) return next(err);

    bcrypt.hash(user.password, salt, function(err, hash) {
      if (err) return next(err);

      user.password = hash;
      next();
    });
  });
});

userSchema.methods.comparePassword = function(password, next) {
  bcrypt.compare(password, this.password, function(err, isMatch) {
    if (err) return next(err);
    next(null, isMatch);
  });
};

const User = mongoose.model("User", userSchema);
module.exports = User;

And this is my blogs collections which has a reference to comments model

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const blogSchema = new Schema(
  {
    title: {
      type: String,
      required: true
    },
    body: {
      type: String,
      required: true
    },
    author: {
      type: Schema.Types.ObjectId,
      ref: "User"
    },
    likesCount: {
      type: Number
    },
    comments: [
      {
        type: Schema.Types.ObjectId,
        ref: "Comment"
      }
    ]
  },
  {
    timestamps: {
      createdAt: "created_at",
      updatedAt: "updated_at"
    }
  }
);


const Blog = mongoose.model("Blog", blogSchema);
module.exports = Blog;

and this is my comments model which has a reference to user model

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const CommentSchema = new Schema(
  {
    body: {
      type: String,
      required: true
    },
    likesCount: {
      type: Number
    },
    user: {
      type: Schema.Types.ObjectId,
      ref: "User"
    }
  },
  {
    timestamps: {
      createdAt: "created_at",
      updatedAt: "updated_at"
    }
  }
);

const Comment = mongoose.model("Comment", CommentSchema);
module.exports = Comment;

what i want is if i fetch user data i want to fetch blog as well as comment data with in i have this code

exports.getCurrentUser = async (req, res) => {
  const ObjectId = mongoose.Types.ObjectId;

  const users = await User.findById({ _id: new ObjectId(req.user._id) })
    .populate({
        path: "blogs",
        model: "Blog"
      })
    .exec();

  console.log(users);

  return res.status(200).json(users);
};

But its not populating blogs

how can i achieve this kind of nesting reference fetching?

1 Answer 1

1

I think the problem is that model must be the model reference, not the model name. So it must be model: Blog instead of the model: "Blog".

Also I suggest that you redesign your schemas, because you have many references between models. And you will have to make 2 db calls when you add, insert or remove a blog or comment.

I would remove blogs fields from user schema, and remove comments field from blog schema, and set up virtual populate like this:

user schema:

const userSchema = new Schema(
  {
    email: {
      type: String,
      required: true,
      index: {
        unique: true
      }
    },
    password: {
      type: String,
      required: true
    },
    name: {
      type: String,
      required: true
    },
    website: {
      type: String
    },
    bio: {
      type: String
    }
  },
  {
    timestamps: {
      createdAt: "created_at",
      updatedAt: "updated_at"
    },
    toJSON: {
      virtuals: true
    }
  }
);

userSchema.virtual("blogs", {
  ref: "Blog",
  foreignField: "author",
  localField: "_id"
});

blog schema:

const blogSchema = new Schema(
  {
    title: {
      type: String,
      required: true
    },
    body: {
      type: String,
      required: true
    },
    author: {
      type: Schema.Types.ObjectId,
      ref: "User"
    },
    likesCount: {
      type: Number
    }
  },
  {
    timestamps: {
      createdAt: "created_at",
      updatedAt: "updated_at"
    },
    toJSON: { virtuals: true }
  }
);

blogSchema.virtual("comments", {
  ref: "Comment",
  foreignField: "blog",
  localField: "_id"
});

Note that, I added toJSON: { virtuals: true } option to the both schemas.

Now you can get a user's blogs with the comments with the following query:

  const user = await User.findById(req.user._id)
    .populate({
      path: "blogs",
      populate: "comments"
    })
    .select("-password")
    .exec();

Test:

Let's say you these sample documents.

db={
  "users": [
    {
      "_id": "5e53b1726f41c765fc4def9c",
      "email": "[email protected]",
      "password": "$2a$10$.heEhkN2BhxZiw8upgjGQe.r3Gt78JVfuAqLqf6lHwireaKJSrrTO",
      "name": "User1"
    },
    {
      "_id": "5e53b1906f41c765fc4def9d",
      "email": "[email protected]",
      "password": "$2a$10$tEaXpoeH5iXVzqmzozAFOOu.Nxb32Ssy1XS5CAqad7qqanHQkrqjK",
      "name": "User2"
    },
    {
      "_id": "5e53b1996f41c765fc4def9e",
      "email": "[email protected]",
      "password": "$2a$10$4s34RLnSd75WeG8K.gzxxeixkruplzW0vpb7PJR/aL1d3Ia31wj.W",
      "name": "User3"
    }
  ],
  "blogs": [
    {
      "_id": "5e53b26c6f41c765fc4def9f",
      "title": "Blog1 Title",
      "body": "Blog1 Body",
      "author": "5e53b1726f41c765fc4def9c"
    },
    {
      "_id": "5e53b2896f41c765fc4defa1",
      "title": "Blog2 Title",
      "body": "Blog2 Body",
      "author": "5e53b1726f41c765fc4def9c"
    }
  ],
  "comments": [
    {
      "_id": "5e53b2f86f41c765fc4defa3",
      "body": "Comment1 (user2 on user1's blog1)",
      "user": "5e53b1906f41c765fc4def9d",
      "blog": "5e53b26c6f41c765fc4def9f"
    },
    {
      "_id": "5e53b3246f41c765fc4defa4",
      "body": "Comment2 (user3 on user1's blog1)",
      "user": "5e53b1996f41c765fc4def9e",
      "blog": "5e53b26c6f41c765fc4def9f"
    },
    {
      "_id": "5e53b34c6f41c765fc4defa5",
      "body": "Comment3 (user2 on user1's blog2)",
      "user": "5e53b1906f41c765fc4def9d",
      "blog": "5e53b2896f41c765fc4defa1"
    }
  ]
}

The result will be like this for user with _id: "5e53b1726f41c765fc4def9c":

{
    "_id": "5e53b1726f41c765fc4def9c",
    "email": "[email protected]",
    "name": "User1",
    "created_at": "2020-02-24T11:20:18.343Z",
    "updated_at": "2020-02-24T11:20:18.343Z",
    "__v": 0,
    "blogs": [
        {
            "_id": "5e53b26c6f41c765fc4def9f",
            "title": "Blog1 Title",
            "body": "Blog1 Body",
            "author": "5e53b1726f41c765fc4def9c",
            "created_at": "2020-02-24T11:24:28.895Z",
            "updated_at": "2020-02-24T11:24:28.895Z",
            "__v": 0,
            "comments": [
                {
                    "_id": "5e53b2f86f41c765fc4defa3",
                    "body": "Comment1 (user2 on user1's blog1)",
                    "user": "5e53b1906f41c765fc4def9d",
                    "blog": "5e53b26c6f41c765fc4def9f",
                    "created_at": "2020-02-24T11:26:48.506Z",
                    "updated_at": "2020-02-24T11:26:48.506Z",
                    "__v": 0
                },
                {
                    "_id": "5e53b3246f41c765fc4defa4",
                    "body": "Comment2 (user3 on user1's blog1)",
                    "user": "5e53b1996f41c765fc4def9e",
                    "blog": "5e53b26c6f41c765fc4def9f",
                    "created_at": "2020-02-24T11:27:32.305Z",
                    "updated_at": "2020-02-24T11:27:32.305Z",
                    "__v": 0
                }
            ],
            "id": "5e53b26c6f41c765fc4def9f"
        },
        {
            "_id": "5e53b2896f41c765fc4defa1",
            "title": "Blog2 Title",
            "body": "Blog2 Body",
            "author": "5e53b1726f41c765fc4def9c",
            "created_at": "2020-02-24T11:24:57.078Z",
            "updated_at": "2020-02-24T11:24:57.078Z",
            "__v": 0,
            "comments": [
                {
                    "_id": "5e53b34c6f41c765fc4defa5",
                    "body": "Comment3 (user2 on user1's blog2)",
                    "user": "5e53b1906f41c765fc4def9d",
                    "blog": "5e53b2896f41c765fc4defa1",
                    "created_at": "2020-02-24T11:28:12.551Z",
                    "updated_at": "2020-02-24T11:28:12.551Z",
                    "__v": 0
                }
            ],
            "id": "5e53b2896f41c765fc4defa1"
        }
    ],
    "id": "5e53b1726f41c765fc4def9c"
}

Another option is using MongoDB aggregation framework. With this option you can remove the options I added for virtual populate.

Playground

  const users = await User.aggregate([
    {
      $match: {
        _id: req.user._id
      }
    },
    {
      $project: {
        password: 0
      }
    },
    {
      $lookup: {
        from: "blogs",
        let: {
          userId: "$_id"
        },
        pipeline: [
          {
            $match: {
              $expr: {
                $eq: ["$$userId", "$author"]
              }
            }
          },
          {
            $lookup: {
              from: "comments",
              let: {
                blogId: "$_id"
              },
              pipeline: [
                {
                  $match: {
                    $expr: {
                      $eq: ["$blog", "$$blogId"]
                    }
                  }
                }
              ],
              as: "comments"
            }
          }
        ],
        as: "blogs"
      }
    }
  ]);

users[0] will give you the same result as option 1.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks virtual one did work..although i tried virtual bymyself before it did work for blogs but wasn't working for comments..thanks once again
@YashGupta you are welcome. I appreciate if you can also upvote the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.