Include count of subdocuments in schema - node.js

In my NodeJS project, I'm creating Mongoose schemas as below:
//Likes Schema
var likesSchema = mongoose.Schema({
postId: { type: mongoose.Schema.Types.ObjectId, ref: "Post", required: 'Provide the news ID to which this comment belongs' },
});
module.exports = mongoose.model('Likes', likesSchema);
//Post schema
var postSchema = mongoose.Schema({
title: { type: String, required: 'Kindly enter the title' },
description: { type: String, required: 'Kindly enter the description of the news' }
});
module.exports = mongoose.model('Post', postSchema);
Post is a schema that has a title and a description. Like is a schema that tracks number of likes for a particular post. So it has just postID.
Now I want to include "count" of likes as a variable in "Post" schema. I don't want to count the likes during query execution.
Is there any simple way to achieve it?

I found a solution after doing some trial and errors:
db.Post.aggregate([
{
$lookup: {
from: "Likes",
localField:"_id",
foreignField: "postId",
as: "likes"
}
},
{
$project: {
title: 1,
description: 1,
count: { $size: "$likes" }
}
}
]).pretty()

Related

how Can I create parent comment which contain children comments in schema mongoose

The idea is about one comment contains children comments, it is about a comment contains responses. For that, I create my Comment schema like this:
import mongoose from 'mongoose';
//to connect to our database
import db from '../db/connectDB.js'
const Schema = mongoose.Schema // Define Schema method= mongoose;
const CommentSchema = new Schema({
id_parent : {
type: Number,
required: true,
},
user_name: {
type: String,
},
content: {
type: String,
},
created_date: {
type: Date,
default: Date.now,
},
counter_like: {
type: Number,
required:false,
},
status : {
type: String,
},
});
export default mongoose.model('Comment', CommentSchema);
then my parentComment schema like this:
import mongoose from 'mongoose';
//to connect to our database
import db from '../db/connectDB.js'
const SchemaParent = mongoose.Schema // Define Schema method= mongoose;
const ParentCommentSchema = new Schema({
parent_comment:{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
},
children_comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}]
});
export default mongoose.model('ParentComment', ParentCommentSchema);
It seems not clean, I don't want to insert the child comment every time I want to insert it directly in the the parent comment, I don't get the idea, any help
You could simply reference the parent ID in the comment itself
const commentSchema = new Schema({
_id,
parentId : { type: mongoose.Schema.Types.ObjectId, ref: 'Comments' },
user: { type: mongoose.Schema.Types.ObjectId, ref 'Users'},
[...]
});
This way you can .find({parentId: commentId}) to find all the comments that would be "answers" of the commentId (parent). You will have an array of Comment
Otherwise you can embed directly the comments inside each others.
const commentSchema = new Schema({
_id,
user: { type: mongoose.Schema.Types.ObjectId, ref 'Users'}
});
commentSchema.add({comments: {type: [commentSchema], default: undefined})
You'd have something like :
comment {
_id: 'qzrqrsrt',
user: '1'
content : "What do you guys think ?"
comments : [
{
_id: 'qzrqzrqzr',
user: '2',
content: 'Ahah nice',
comments: [
{
_id: 'qiorqzpçrq',
user: '1',
content: 'Thanks'
}
]
},
{
_id: 'dryhsertse',
user: '2'
content: "I'll use it"
}
]
}
But that would be a bit more difficult to find the comment you want to add answers to

How to populate the result of a $lookup

I have 3 collections, posts, comments and users, the schemas for posts and comments are below:
post schema
const PostSchema: Schema = new Schema({
author: { type: String, ref: 'User' },
postID: {type: String},
text: { type: String },
});
comment schema
const commentSchema: Schema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'User' },
parentPost: {type: Schema.Types.ObjectId, ref: 'Post'},
parentPostID: {type: String}, // Post that was commented on
});
To get the comment from each post, I do an aggregate query:
const aggregateQuery = [
{
$match:{postID:{ $in: postKeys},},
},
{
$lookup:
{
from: 'comments',
localField: '_id',
foreignField: 'parentPost',
as: 'Top_Comment',
},
},
{
$sort: {
createdAt: 1,
}
,
},
]
That query works, but I can't figure out how to populate the author property of the comment since its a ref to a user.
What I tried
I tried using pipeline, but I'm a new to aggregation so I didn't know how, any ideas and solutions would be really appreciated

Populating array of collection which contains references to another collections returns empty array

I have two models Vote and Link,I am trying to populate the votes array in link model,The votes array contains id's that references to the collection Vote,which only contains two fields link and User which also refs to same link model mentioned below and a user model respectively
link Schema:-
const linkSchema = new mongoose.Schema(
{
description: {
type: String,
trim: true,
},
url: {
type: String,
trim: true,
},
postedBy: {
type: mongoose.Types.ObjectId,
ref: "User",
},
votes: [{ type: mongoose.Types.ObjectId, ref: "Vote" }],
},
{
timestamps: true,
}
);
linkSchema.index({ description: "text" });
linkSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Link", linkSchema);
Vote schema:-
const mongoose = require("mongoose");
const voteSchema = new mongoose.Schema({
link: { type: mongoose.Types.ObjectId, ref: "Link" },
user: { type: mongoose.Types.ObjectId, ref: "User" },
});
module.exports = mongoose.model("Vote", voteSchema);
but when i try to get the votes of a link,it always return an empty array ,My function:-
const votes = async ({ id }) => {
const linkData = await Link.findById(id).populate("votes").exec();
console.log(linkData);
};
Output Data:-
{
votes: [], //empty always
_id: 5ecb21059a157117c03d4fac,
url: 'https://www.apollographql.com/docs/react/',
description: 'The best GraphQL client for React',
postedBy: 5ec92a58bf38c32b38400705,
createdAt: 2020-05-25T01:36:05.892Z,
updatedAt: 2020-05-25T01:37:52.266Z,
__v: 0
}
Instead of populate(), you can use aggregate() to get your desired output. This should probably work in your case:
Link.aggregate([
{
$match: {
_id: { $in: [mongoose.Types.ObjectId(id)] // as suggested by the questioner
}
},
{
$lookup: {
from: "vote", // collection to join
localField: "votes", // field from the input documents (filtered after _id is matched)
foreignField: "link", // field to compare with, from other collection
as: "linkData" // output array name
}
}
])
Let me know in the comments.

How to query documents with reference fields parameters in Mongoose?

I have the following schemas (Product, ProductCategory):
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
productCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ProductCategory'
}
})
const productCategorySchema = new mongoose.Schema({
name: {
type: String,
required: true
}
})
What I would like to do is to query all the Product documents who has a certain Product.productCategory.name = ?
I read about population in mongodb but can't really know how to apply it here.
You could use aggregation function '$lookup'
db.productCategory.aggregate([{
$match: {
name: "{category_name}"
}
}, {
$lookup: {
from: 'product',
localField: '_id', // ProductCategory._id
foreignField: 'type', // Product.product_category_id
as: 'products'
}
}]);

MongoDB parent / child relationinship and fast retrieval of parents

I have the following models in mongoose, Book which belonds to a User.
When I Book.findAll(), I also want to retrieve information from the associated User. What is the best way to achieve this?
const user_schema = new mongoose.Schema({
fullname: { type: String },
avatar: { type: String }
});
module.exports.User = mongoose.model("User", user_schema);
const book_schema = new mongoose.Schema({
_uid: { type: ObjectId, ref: "User", required: true },
title: { type: String, required: true }
text: { type: String, required: true }
});
module.exports.Book = mongoose.model("Book", book_schema);
I need to be able to do something like:
Book.findOne({...}).user.avatar
You could use $lookup for this kind of operations.
db.books.aggregate([
{
$lookup:
{
from: "users",
localField: "user_id", //User ID in users collection
foreignField: "_uid", //User ID in books collection
as: "books_data"
}
}
])
Also, kindly have primary key values in both schemas:
const user_schema = new mongoose.Schema({
user_id : {type : String },
fullname: { type: String },
avatar: { type: String }
});

Resources