Filter collection on associated field ObjectId - node.js

SEE EDIT AT BOTTOM OF QUESTION.
I have a Node.js Express web application using MongoDB and Mongoose with collections for articles and comments. They have a one-to-many association where one article can have many comments.
The mongoose model schema is as follows:
// models/article
const mongoose = require('mongoose');
const articleSchema = new mongoose.Schema({
title: { type: String },
content: { type: String },
}, {timestamps: true});
module.exports = mongoose.model('Article', articleSchema);
and
// models/comment.js
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
content: { type: String },
article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article' },
}, {timestamps: true});
module.exports = mongoose.model('Comment', commentSchema);
I have a route with a parameter for the article id
// routes.js
router.get('/articles/:articleId/comments', commentsController.list);
And a controller with a callback function to query the database and return the comments with the given article id. It uses the mongoose find() method filtering on the article id taken from the route parameter.
// controllers/commentsController.js
exports.list = (req, res, next) => {
Comment.find({ article: req.params.articleId })
.exec((err, comments) => {
res.render('comments/list', { title: 'Comments', comments: comments });
});
};
But this turns up no results. Just experimenting I can see that the req.params.articleId is a string and any comment.article is an object so they match with a loose comparison == but not a strict comparison === unless I convert comment.article.toString(). Anyway, what is the proper way to do such a query. All my attempts have failed.
EDIT: I found the problem. The code above is as it should be. The issue must be related to how I seeded the DB which I did directly in MongoDB. I deleted all those records and just added them from the application and it works with the code above.

One way to approach this is to add the comments to your article model.
const mongoose = require('mongoose');
const articleSchema = new mongoose.Schema({
title: { type: String },
content: { type: String },
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
}, {timestamps: true});
articleSchema.set('toJSON', {
transform: (document, returnedObject) => {
const article = returnedObject
article.id = article._id.toString()
delete article._id
}
})
module.exports = mongoose.model('Article', articleSchema);
Then get the comments in one of these ways:
const router = require('express').Router()
const Article = require('../models/article')
const Comment = require('../models/comment')
// article with comments
router.get('/:id', async (request, response, next) => {
try {
const article = await Article.findById(request.params.id)
.populate(
'comments', {
content: 1
}
)
response.json(article.toJSON())
} catch (err) {
console.log(err)
}
})
// list of comments belonging to an article
router.get('/:id/comments', async (request, response, next) => {
try {
const article = await Article.findById(request.params.id)
if (!article) {
response.status(404).json({ error: 'invalid request' })
}
const comments = await Comment.find({ article: request.params.id })
.populate(
'article', {
title: 1
}
)
response.json(comments.map(comment => comment.toJSON()))
} catch (err) {
console.log(err)
}
})
module.exports = router

Related

Nodejs TypeError: Cannot read property 'push' of undefined

hey i am new here in nodejs and mongodb, i tyied to push comments on post in my social media project..
Here is my controller ,it shows error while pushing comments in mongodb
TypeError: Cannot read property 'push' of undefined
const Comment = require('../models/comment')
const Post = require('../models/post')
module.exports.create = function(req,res){
Post.findById(req.body.post, function(err ,post){
if(post){
Comment.create({
content: req.body.content,
post: req.body.post,
user: req.body._id
},function(err, comment){
if(err){console.log("error in pushing comment")}
post.comments.push(comment),
post.save()
res.redirect('/')
})
}
})
}
this is my comments schema
const mongoose = require('mongoose')
const commentSchema = new mongoose.Schema({
content: {
type: String,
required: true
},
//comments belongs to user
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
post : {
type: mongoose.Schema.Types.ObjectId,
ref : 'Post'
}
},{
timestamps: true
})
const Comment = mongoose.model('Comment' , commentSchema)
module.exports = Comment
I recommend that you use Promises instead of callback functions. It will make your code way more readable. Monogoose findOneAndUpdate could be handy here.
As for the error, you should make a console.log(post.comments) to see the value for yourself.
We should see the model of Post. It should contain an array of Comments
const mongoose = require('mongoose')
const Comment = require('./comment.model.js') // Change the path
const postSchema = new mongoose.Schema({
// comments belongs to post
comments : {
type: [Comment]
},
// Other attributes here
},{
timestamps: true
})
const Post = mongoose.model('Post' , postSchema)
module.exports = Post
You'll end up with something like this:
const Comment = require('../models/comment')
const Post = require('../models/post')
module.exports.create = function (req, res) {
Post.findById(req.body.post).then((post) => {
if (post) {
Comment.create({
content: req.body.content,
post: req.body.post,
user: req.body._id
}).then((comment) => {
Post.findOneAndUpdate(
{ _id: req.body.post },
{ $push: { comments: comment } }
).then(() => {
res.redirect('/')
}).catch((error) => console.log(error))
})
}
}).catch((error) => console.log(error))
}

Route for getting a review for a book in a MERN-stack book review app?

I am trying to write a MERN stack app used for reviewing books.
I have 2 MongoDB (Mongoose) schemas: models/Book.js and models/Review.js, where one Book can have multiple Reviews.
These are my schemas:
models/Review.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let reviewSchema = new Schema(
{
text: {
type: String
},
book: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
}
}
);
module.exports = mongoose.model('Review', reviewSchema)
models/Book.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let bookSchema = new Schema(
{
title: {
type: String
},
detail: {
type: String
}
}, {
collection: 'books'
}
);
module.exports = mongoose.model('Book', bookSchema)
How should a route look like if I want to get all data about a single book via this URL: /book/<book_id_from_mongodb>?
I can only get Book's data but I don't know how to get all Reviews that belong to that particular book:
/routes/book.route.js:
let mongoose = require('mongoose'),
express = require('express'),
router = express.Router();
let bookSchema = require('../models/Book');
let reviewSchema = require('../models/Review');
// Get a single book - HOW SHOULD I MODIFY THIS ROUTE TO GET ALSO ALL REVIEWS FOR THAT BOOK ?
router.route('/book/:id').get((req, res) => {
bookSchema.findById(req.params.id, (error, data) => {
if (error) {
res.status(500).send(err);
} else {
res.status(200).json(data)
}
})
})
I tried this (not working):
router.route('/book/:id').get((req, res) => {
bookSchema.findById(req.params.id, (error, data) => {
if (error) {
res.status(500).send(err);
} else {
var bookData = data;
reviewSchema.find({"book": mongoose.Schema.Types.ObjectId(req.params.id)}, (error, data) => {
if (error) {
res.status(500).send(err);
} else {
var reviewData = data;
bookData.reviews = reviewData;
res.status(200).json(bookData);
}
});
}
})
})
The best way to do it would be to put a reviews element into your bookSchema and reference the ObjectId of the review.
Then on your get route, use .populate("reviews").exec(your callback).
Book Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let bookSchema = new Schema(
{
title: {
type: String
},
detail: {
type: String
}
}, {
collection: 'books'
}, {
reviews: [
type: mongoose.Schema.Types.ObjectId,
ref: 'Review'
]
}
);
module.exports = mongoose.model('Book', bookSchema)
Route
let mongoose = require('mongoose'),
express = require('express'),
router = express.Router();
let bookSchema = require('../models/Book');
let reviewSchema = require('../models/Review');
// Get a single book - HOW SHOULD I MODIFY THIS ROUTE TO GET ALSO ALL REVIEWS FOR THAT BOOK ?
router.route('/book/:id').get((req, res) => {
bookSchema.findById(req.params.id).populate("reviews").exec((error, data) => {
if (error) {
res.status(500).send(err);
} else {
res.status(200).json(data)
}
})
})
When creating a review you need to push it to the bookSchema.reviews and then use bookSchema.save() to it.
Let me know if it does not work.
I may have missed something.

How to join 2 collections in mongoose using nodejs

I want to join two collection using mongoose nodejs but i am stuck,
collection1
collection2
const mongoose = require('mongoose');
const gameSchema = new mongoose.Schema({
providerName:{
type: String
},
gamesSettings :[{
type: mongoose.Schema.Types.ObjectId,
ref: 'games_setting'
}]
});
module.exports = mongoose.model('gamesDetails', gameSchema);
This is the route :
router.get('/', async (req, res)=>{
try {
const gamesDetails1 = await joinCollection.find();
res.json(gamesDetails1);
//res.render('./games/gamesetting', { data: gamesDetails1 });
} catch (e) {
res.json({ message: e });
}
});
I am getting null in response.
I'm not sure that I understood your question correctly but I'm thinking that what you need is to execute a query where you get gameeSetting populated. The answer to that would be:
const details = await gamesDetails.find().populate('gamesSettings');

Cascade Delete in mongo

I am new to MongoDB. I created 4 collections & they are connected with each other. (I am using node.js to write it)
Here, it's my question. How can I delete all records at once? Is there something like deep level population?
This one holds all models.
const DataModel = mongoose.Schema({
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Order', require: true},
});
User model
const userSchema = mongoose.Schema({//other stuff});
Order model
const orderSchema = mongoose.Schema({
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
//other stuff
});
Product model
const productSchema = mongoose.Schema({//other stuff});
I can delete the entry with these code from the database, but the other entries still there
exports.delete_data = (req, res, next) => {
const id = req.params.userId;
userDataModel.deleteOne({_id: id})
.exec()
.then(docs => {
res.status(200).json({
message: 'Record Deleted',
request: {
type: 'POST'
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
};
Update: However, I wonder, Could I call other defined delete functions for order, product inside delete_data
As #Geert-Jan suggest, cascade delete is my solution. The link that geert-jan gave solve my problem. However, I wonder, Could I call other defined delete functions for order, product inside delete_data
i did this and it could be good for someone who wants to delete documents in cascade linked to any field of a model.
async blackHole() {
try {
const rtn = new ApiResponse<any>();
const userId = id;
const accountId = mongoose.Types.ObjectId(id);
var CollectionNames: any[] = [];
mongoose.connection.db.listCollections().toArray(function (err, collections) {
CollectionNames = collections.map(c => c.name);
CollectionNames.forEach((element: any) => {
mongoose.connection.db.collection(element).deleteMany({ "account": accountId });
});
});
const accountController = new AccountController(this.wsParams);
await accountController.delete(id)
await super.delete(userId);
return rtn;
} catch (error: any) {
const rtn = new ApiResponse<any>();
rtn.message = error;
rtn.success = false;
rtn.status = 422;
return rtn;
}
}
I hope you can use it :D

Mongoose populating isn't working

I'm trying to populate post with comments. With populating author I didn't have any problems. I tried to populate just comments without author and it didn't work..
Here is the comment model:
const mongoose = require('mongoose');
const schema = mongoose.Schema;
const User = require('./user');
commentSchema = new schema({
comment: String,
author: { type: schema.Types.ObjectId, ref: 'User' },
})
const Comment = module.exports = mongoose.model('Comment', commentSchema);
Here is the route:
router.get('/posts/:id', (req, res) => {
Post.findById({ _id: req.params.id })
.populate('author')
.populate('comments')
.exec((err, post) => {
if (err) {
console.log(err);
} else {
res.json(post);
}
});
});

Resources