'populate' and working with parent / child models in Mongoose / MongoDB - node.js

I have a pretty simple setup where I'm trying to populate my Mongoose JSON responses with all Comments that belong to a Post
I thought that calling 'populate' on Post would return all comments related to the Post, but instead I'm getting an empty array. I just don't get what I'm doing wrong.
post.js
const mongoose = require('mongoose');
const db = require('./init');
const postSchema = new mongoose.Schema({
title: String,
url: String,
body: String,
votes: Number,
_comments: [{type: mongoose.Schema.Types.ObjectId, ref: "Comment"}]
});
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
comment.js
const mongoose = require('mongoose');
const db = require('./init');
const commentSchema = new mongoose.Schema({
// post_id: post_id,
_post: { type: String, ref: 'Post'},
content: String,
posted: { type: Date, default: Date.now() }
});
const Comment = mongoose.model('Comment', commentSchema);
module.exports = Comment;
posts.js
router.get('/', function(req, res, next) {
// An empty find method will return all Posts
Post.find()
.populate('_comments')
.then(posts => {
res.json(posts)
})
.catch(err => {
res.json({ message: err.message })
})
});
and within the posts.js file I've set up a route to create a comment when a post request is sent to posts/post_id/comments
commentsRouter.post('/', function(req, res, next) {
console.log(req.params.id)
//res.json({response: 'hai'})
comment = new Comment;
comment.content = req.body.content;
comment._post = req.params.id
comment.save((err) => {
if (err)
res.send(err);
res.json({comment});
});
});
Comments are being created when I post to this route, and they are created with the correct _post value, however populate isn't picking them up.
For example, this post has been created, and it doesn't populate the associated comment below:
{
"post": {
"__v": 0,
"votes": 0,
"body": "Test Body",
"url": "Test URL",
"title": "Test Title",
"_id": "587f4b0a4e8c5b2879c63a8c",
"_comments": []
}
}
{
"comment": {
"__v": 0,
"_post": "587f4b0a4e8c5b2879c63a8c",
"content": "Test Comment Content",
"_id": "587f4b6a4e8c5b2879c63a8d",
"posted": "2017-01-18T10:37:55.935Z"
}
}

When you create a comment, you also have to save the comment instance _id to a post. So within the save() callback, you can do something like
commentsRouter.post('/', function(req, res, next) {
console.log(req.params.id)
//res.json({response: 'hai'})
comment = new Comment({
content: req.body.content;
_post: req.params.id
});
comment.save((err, doc) => {
if (err)
res.send(err);
Post.findByIdAndUpdate(req.params.id,
{ $push: { _comments: doc._id } },
{ new: true },
(err, post) => {
if (err)
res.send(err);
res.json({doc});
}
)
});
});

Related

Filter collection on associated field ObjectId

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

how can i access mongodb document's array? (mongodb, nodejs)

Hi i have some problems with mongodb.
const mongoose = require('mongoose');
const companySchema = new mongoose.Schema({
name: String,
url: String,
images: Array
});
const categoriesSchema = new mongoose.Schema({
company: companySchema,
name: String
});
module.exports = mongoose.model('categories', categoriesSchema);
above of code is model
app.post('/addCompanyinfo', function (req, res){
var news = new Categories();
news.company[name]= req.body.company; <--- here!
news.save(function (err) {
if(err) {
console.error(err);
res.json({result: 0});
return;
}
res.json({result: 1})
})
})
and this is router code. and i want to access
categoriesSchema-> company (companySchema)-> name.
how can i access company schema's 'name' ??
your help will be a big lucky in future to you :)
You need to create an Object for Company to assign its properties
var news = new Categories();
var company = new Company();
news.company = company;
news.name = 'test category'
company.name = 'test company';
console.log(company);
console log
{ name: 'test category',
company: { _id: 5a61629b74f8df0bd73142ba, images: [] },
_id: 5a61629b74f8df0bd73142b9 }
{ name: 'test company', _id: 5a61629b74f8df0bd73142ba, images: [] }
You can post your data in json format and use bodyParser.json() middleware in your app.js, and your backend code can be this:
router.post('/', function(req, res, next) {
var news = new Categories(req.body); // create record directly with the json data
console.log(req.body.company.name); //just access it directly
news.save(function (err) {
if(err) {
console.error(err);
res.json({result: 0});
return;
}
res.json({result: 1});
});
});
and json format can be this:
{
"company": {
"name": "test company name",
"url": "http://test.com",
"image": []
},
"name": "test name"
}

using mongoose population in Express

There a lot of great examples in adding data in Schemas using population
But how to use this in Express including evaluation existing data and error handling? I already create an user but now I want to add a log to a existing user.
This is my Schema:
const logSchema = new Schema({
logTitle: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId, ref: 'User'
}
});
const userSchema = new Schema({
_id: Schema.Types.ObjectId,
firstName: {
type: String,
required: true
}
});
mongoose.model('User', userSchema);
mongoose.model('Logs', logSchema);
Despite all the good examples I lost my way. It 's hard to use the nice examples in good working Express code.
const mongoose = require('mongoose')
const Log1 = mongoose.model('Logs');
const logCreate = function (req, res) {
const userid = req.params.userid;
Log1.create({
logTitle: req.body.logTitle,
postedBy: userid._id
});
module.exports = {logCreate
};
Do I first create a new log in 'Logs' and then evaluate the existing user? Can somebody give me a push in the right direction? Thanks in advance
You have to add an objectid to the postedBy field. Let's say you have the following routes:
router
.route('/logs/')
.get(ctrlLogs.logsGetAll)
.post(ctrlLogs.logsAddOne);
router
.route('/logs/:logid')
.get(ctrlLogs.logsGetOne);
Then we can define the controller functions as (this is ctrlLogs):
const mongoose = require('mongoose');
const Log = mongoose.model('Log');
module.exports.logsAddOne = (req, res) => {
Log.create({
logTitle: req.body.logTitle,
postedBy: req.body.postedBy
}, (err, newLog) => {
let response = {};
if (err) response = { status: 500, message: err };
else response = { status: 201, message: newLog };
return res.status(response.status).json(response.message);
});
};
module.exports.logsGetAll = (req, res) => {
Log
.find({})
.populate('postedBy')
.exec((err, logs) => {
let response = {};
if (err) response = { status: 500, message: err };
else if (!logs) response = { status: 404, message: [] };
else response = { status: 200, message: logs };
return res.status(response.status).json(response.message);
});
};
module.exports.logsGetOne = (req, res) => {
Log
.findById(req.params.logid)
.populate('postedBy')
.exec((err, log) => {
let response = {};
if (err) response = { status: 500, message: err };
else if (!log) response = { status: 404, message: `Log with id ${req.params.logid} not found.` };
else response = { status: 200, message: log };
return res.status(response.status).json(response.message);
});
};
I've removed a lot of whitespace and curly braces for brevity. You should also not create stuff with values directly from req.body or query directly with req.params.logid; the values have to be validated first. You can use a library such as express-validator for validation.
We can see that by posting a request to /logs with the following request body:
{
"logTitle": "test log 32",
"postedBy": "5a2d1ec83ef998431c726dfc"
}
we will create a log where the user with id 5a2d1ec83ef998431c726dfc is set as the postedBy user. If I get /logs/5a2d240c66d7b473a0aa6ec8 (the new log) I get:
{
"_id": "5a2d240c66d7b473a0aa6ec8",
"logTitle": "test log 32",
"postedBy": {
"_id": "5a2d1ec83ef998431c726dfc",
"firstName": "test",
"__v": 0
},
"__v": 0
}
We can see that the populated log returns the user as well.

Mongoose update is not working

My question here is why isn't the watchlistSchema.update(function (error) { { $push: { watchlist: req.body.stockAdded } }}); line updating the existing schema for the watchlist attribute? When I use this update nothing happens and it returns null. When I change it to watchlistSchema.save it work but it creates and entirely different document. I would like to basically check for the user and watchlist and if they both exist together I would like to push a string into the watchlist array. I am very new to mongoose so it is a bit confusing.
var Schema = mongoose.Schema;
var watchlistSchema = new Schema({
watchlist: [{ }],
user: String
});
var Watchlist = mongoose.model('Watchlist', watchlistSchema, "watchlist");
app.post('/watchlistPost', function (req, res) {
var watchlistSchema = Watchlist({
'watchlist': req.body.stockAdded,
'user': req.user.username
});
Watchlist.findOne({
$and: [{
'watchlist': req.body.stockAdded,
}, {
'user': req.user.username
}]
}, function (err, list) {
if (list) {
res.status(200).send({ "success": "Updated Successfully", "status": 200 });
} else {
if (req.user) {
watchlistSchema.update(function (error) {
{ $push: { watchlist: req.body.stockAdded } }
});
} else {
}
}
})
});
Your update statement needs to contain the "find" query. So it can appy the update condition to all documents matching the specified query.
change your code to something like:
var Schema = mongoose.Schema;
var watchlistSchema = new Schema({
watchlist: [{ }],
user: String
});
var Watchlist = mongoose.model('Watchlist', watchlistSchema, "watchlist");
app.post('/watchlistPost', function (req, res) {
var watchlistSchema = Watchlist({
'watchlist': req.body.stockAdded,
'user': req.user.username
});
var query = {
$and: [{
'watchlist': req.body.stockAdded,
}, {
'user': req.user.username
}]};
Watchlist.update(query, { $push: { watchlist: req.body.stockAdded } }, ==your callback to check stuff==);

Adding slug to req params url in express

I'm using the 'slug' npm library to give-strings-dashes-for-url-cleanliness. The library works when I console.log() a string, and it's required properly into all of the relevant controllers.
However, I can't figure out how implement slug() properly to format my URLs. The problem I'm having is that a product name might be "Foo Bar Baz Quux", but I can't seem to find the right implementation for slug() without disrupting the connection between the app.js route and the findOne query via mongoose.
app.js
app.get('/market/:product_name', marketController.getProduct);
controller.js
exports.getProduct = function(req, res, next) {
var product_name = req.params.product_name;
// var slugProduct = slug(product_name);
Product.findOne({'name': product_name}, function(err, product) {
if (err) return next(err);
return res.send(product.data);
});
}
Perhaps you could "slugify" a product by defining a slug property while setting up your "Product Schema". I can confirm it works if using mongoose with along mongoose-slug-generator plugin, see link... You could set it up as follows:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const slug = require('mongoose-slug-generator');
// * mongoose slug generator options
const options = {
separator: '-',
lang: 'en',
truncate: 120
};
// * Init mongoose slug generator plugin
mongoose.plugin(slug, options);
const ProductSchema = new Schema({
name: {
type: String,
trim: true
},
slug: {
type: String,
slug: 'name', // genarating slug from multiple properties is allowed ['name', 'brand']
unique: true
},
price: {
type: Number,
required: true
},
brand: {
type: String,
default: 'Apple'
}
});
module.exports = mongoose.model('Product', ProductSchema);
exports.getProduct = function(req, res, next) {
const { product_name } = req.params;
Product.findOne({ slug: product_name }, function(err, product) {
if (err) return next(err);
if (!product) {
return res.status(404).json({
message: 'Product data not found.'
);
}
res.status(200).send(product);
});
}
app.get('/market/:product_name', marketController.getProduct)
// request
const response = await axios.get('/api/market/my-awesome-product')
// response.data
{
"_id": "60fh4d37ac1a1c6f58d6a5f4",
"name": "My Awesome Product",
"slug": "my-awesome-product",
"price": 85.9,
"brand": "Apple"
}

Resources