Mongoose How to sort .populate field - node.js

I'm work with an user/articles profile system. I have been using the .populate() to render the posts but I cannot get the articles sorted by the date they were created.
I am using the createdAt variable as the main way of ordering the posts displayed.
For reference:
router.get('/:id', async (req, res) => {
const user = await User.findById(req.params.id, function(error) {
if(error) {
req.flash("error", "something went wrong")
res.redirect("/");
}
}).populate('articles')
res.render('users/show',{
user: user
});
and the article.js:
const ArticleSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
author: {
type: String
},
markdown: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
slug: {
type: String,
required: true,
unique: true
},
sanitizedHtml: {
type: String,
required: true
},
img: {
type: String
},
type:{
type: String
},
user : { type: Schema.Types.ObjectId, ref: 'User' },
}, {timestamps: true});
In advance thank you all for the help.

There is a property called options in populate,
.populate({
path: 'articles',
options: { sort: { createdAt: -1 } }
})

Related

How to get a certain field of an embedded MongoDB document knowing its ObjectId

I have two collections in MongoDB database, Post and User. Each post contains the creator's ObjectId. When fetching all posts from the database, I want to add the name information, which is a field of User/creator. I tried to access the name field by post.creator.name, but it is not working.
const postSchema = new Schema(
{
title: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
imageUrl: {
type: String,
required: true,
},
creator: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
{ timestamps: true }
);
const userSchema = new Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
status: {
type: String,
required: true,
},
isExpert: {
type: Boolean,
required: true,
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post",
},
],
});
exports.getPosts = (req, res, next) => {
Post.find({}).then((posts) => {
const postsWithAuthorName = posts.map(post => {
return {
...post,
name: post.creator.name
}
})
res.status(200).json({
posts: postsWithAuthorName,
});
});
};
Try this
Post.find({}).populate('creator', 'name')
See populate doc for reference.
When you query posts, creator is just an ObjectId, so you can't just call .name on it. To make it work you need to lookup this data by using e.g. populate.

Mongoose do not populate objectid in an objectid of array

THIS PROBLEM IS A LITTLE LONGER. SO I TYPED BOLD THE CRITICAL INFORMATIONS FOR YOU.
I develop a project like stackoverflow. I have 4 databases which are:
problems
users
solutions
comments
I referrenced these schemas each other. Here is the Schemas:
Problem Schema
const problemSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'You have to enter a title']
},
content: {
type: String,
required: [true, 'You have to enter a content']
},
createdAt: {
type: Date,
default: Date.now()
},
slug: {
type: String
},
solution: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Solution'
},
],
comment: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
],
votes: {
type: Number,
default: 0
},
views: {
type: Number,
default: 0
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
}
})
module.exports = mongoose.model('Problem', problemSchema)
User Schema:
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: [true, 'You have to enter an email'],
unique: true,
match: [
/^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/,
'Please provide a valid email address.'
]
},
password: {
type: String,
required: [true, 'You have to enter a password'],
minlength: [6, 'Your password cannot be less than 6 character.'],
select: false
},
role: {
type: String,
default: 'user',
enum: ['user', 'admin']
},
createdAt: {
type: Date,
default: Date.now()
},
about: {
type: String
},
place: {
type: String
},
age: {
type: Number
},
blocked: {
type: Boolean,
default: false
},
problem: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Problem'
},
],
solution: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Solution'
}
],
comment: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
})
and Comments Schema:
const commentSchema = new mongoose.Schema({
content: {
type: String,
required: [true, 'You have to enter a content']
},
createdAt: {
type: Date,
default: Date.now()
},
isFunctional: {
type: Boolean,
default: false
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
problem: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Problem'
},
})
module.exports = mongoose.model('Comment', commentSchema)
In my project, I send problems into MongoDB. Then I send comment. After save comments, I add these comments into problems and user DB with a function.
function that comments are saved in DB:
const Comment = require('../models/comment/Comment')
const Problem = require('../models/problem/Problem')
const User = require('../models/user/User')
const asyncErrorWrapper = require('express-async-handler')
const addCommentToProblem = asyncErrorWrapper(async (req, res, next) => {
const {content, problemId} = req.body
const newComment = await Comment.create({
content: content,
problem: problemId,
user: req.user.id,
})
const problemOfComment = await Problem.findByIdAndUpdate(problemId, {
$push: { comment: newComment._id }
})
const userOfComment = await User.findByIdAndUpdate(req.user.id, {
$push: { comment: newComment._id }
})
})
Okey everything is so far so good. The problem comes here. When I try to get a problem, I populate some fields for example user fields. So I can add user information in this detail of problem. When populate user and comment in problem schema, it sends me the data. Still, we're ok. But when I try to get user field in comments, it doesn't populate user. It turns just objectId of user information.
Here is the function that I get problem:
const getAProblem = asyncErrorWrapper(async (req, res, next) => {
const {id} = req.params
const problems = null
await Problem.findByIdAndUpdate(id, {
$inc: { views: 1 }
}, { new: true })
.populate('user') ==> THIS LINE WORKS
.populate('comment') ==> THIS LINE WORKS
.populate('comment.user') ==> THIS LINE DOES NOT WORK
.exec(function(err, post) {
if(err) {
console.log(err)
}
res
.status(200)
.json({
success: true,
data: post
})
});
})
Thanks for reading and your patience. Any help will be appreciated.
See doc at https://mongoosejs.com/docs/populate.html
And try this way.
const getAProblem = asyncErrorWrapper(async (req, res, next) => {
const {id} = req.params
const problems = null
await Problem.findByIdAndUpdate(id, {
$inc: { views: 1 }
}, { new: true })
.populate('user') ==> THIS LINE WORKS
.populate({
'path': 'comment',
'populate': {
'path':'user'
}
})
.exec(function(err, post) {
if(err) {
console.log(err)
}
res
.status(200)
.json({
success: true,
data: post
})
});
})

How do I remove an array of referenced Objects when deleting the main document?

This is my MongoDB schema:
const MenuSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
caterer: {
type: Schema.Types.ObjectId,
ref: 'User'
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
}, { timestamps: true })
const ProductSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
price: {
type: String
}
}, { timestamps: true })
What I'm wondering - is how I can delete the array of products, at the same time as I delete the main "Menu" document? When I remove the Menu, I can also assume that the products belonging to the menu should be removed.
At the moment this is how I remove the menu (and tried to remove its products):
await Menu.findOneAndDelete({ _id: req.params.menu_id }, (err, response) => {
if (err) {
console.error(err);
}
Product.remove({ _id: { $in: req.body.products }}, (err, res) => {
if (err) {
console.error(err);
}
console.log('Deleted products');
});
});
However, the products do not get removed. Any suggestions?
Mongoose provides a pre and post middleware on your schema. Which means you can delete all the referenced documents before or after you do an operation on the current schema.
Read more here.
Here's an example, inside your schema add this:
const MenuSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
caterer: {
type: Schema.Types.ObjectId,
ref: 'User'
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
}, { timestamps: true })
const ProductSchema = new Schema({
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
description: {
type: String,
require: true
},
image: {
type: String,
},
price: {
type: String
}
}, { timestamps: true })
MenuSchema.post('remove', removeProducts);
function removeProducts(doc) {
Products.remove({_id: { $in: doc.products}})
}
Assuming Products is the name of your model.
Try This It works for Me.
await Menu.findOneAndDelete({ _id: req.params.menu_id }, (err, response) => {
if (err) {
console.error(err);
}
Product.remove({ _id: { $in: response.products }}, (err, res) => {
if (err) {
console.error(err);
}
console.log('Deleted products');
});
});
You can use post schema hooks of mongoose as below
schema.post('remove', function(doc) {
console.log('%s has been removed', doc._id);
});
Mongoose Post Hook
But the best approach is to use transactions to execute multiple operations on the database as below.
let session = null;
db.startSession()
.then((_session) =>{
session = _session;
session.startTransaction();
return Menu.deleteOne({ _id: req.params.menu_id });
})
.then(()=> Product.deleteMany({ _id: { $in: req.body.products }}))
.then(()=>{
session.commitTransaction();
})
.catch((err)=>{
session.abortTransaction()
return handleError(err);
})
Mongoose Transactions

Nodejs:Array data not added in mongodb

This is my model profile.js
var mongoose = require('mongoose');
const ProfileSchema = mongoose.Schema({
educationinfo: [{
universityname:
{
type: String,
required: true
},
degree:
{
type: String,
required: true
},
coursecompletionyear:
{
type: String,
required: true
},
collegename:
{
type: String,
required: true
},
specialization:
{
type: String,
required: true
},
marks:
{
type: String,
required: true
},
courselevel:
{
type: String,
required: true
}
}]
});
const Profile = module.exports = mongoose.model('Profile', ProfileSchema);
This is my route.js post function
router.post('/addprofiledetails', function(req, res, next) {
let newProfile = new Profile({
$educationinfo:[{
universityname:req.body.universityname,
degree:req.body.degree
}]
});
newProfile.save((err, profile) => {
if (err) {
res.json({ msg: 'Failded to add profiledetails' });
} else {
res.json({ msg: 'successfully add profile details' });
}
});
});
I got success msg in post function but the data not added in mongodb. i don't know where i did mistake .please help me.
In mongoDB I got data like,
{
"educationinfo": [],
"_id": "5bed14b93a107411a0334530",
"__v": 0
}
I want details inside educationinfo, please help.
You need to change schema definition and query.
1.remove required from schema Or apply required to those field that you must provide value.
educationinfo: [{
universityname:
{
type: String,
// required: true
},
degree:
{
type: String,
//required: true
},
coursecompletionyear:
{
type: String,
// required: true
},
collegename:
{
type: String,
// required: true
},
specialization:
{
type: String,
//required: true
},
marks:
{
type: String,
// required: true
},
courselevel:
{
type: String,
// required: true
}
}]
2.change $educationinfo with educationinfo
educationinfo:[{
universityname:req.body.universityname,
degree:req.body.degree
}]
Since you marked the properties of educationinfo as required, you need to provide them when you create an instance of Profile. If you don't want to do that you need to remove the required property from those properties that you won't be supplying on instance creation like below:
const mongoose = require('mongoose');
const ProfileSchema = mongoose.Schema({
educationinfo: [{
universityname:
{
type: String,
required: true
},
degree:
{
type: String,
required: true
},
coursecompletionyear:
{
type: String
},
collegename:
{
type: String
},
specialization:
{
type: String
},
marks:
{
type: String
},
courselevel:
{
type: String
}
}]
});
const Profile = module.exports = mongoose.model('Profile', ProfileSchema);
After making those changes, you need to make one more change in your POST route, change $educationinfo to educationinfo
router.post('/addprofiledetails', function(req, res, next) {
const newProfile = new Profile({
educationinfo:[{
universityname: req.body.universityname,
degree: req.body.degree
}]
});
newProfile.save((err, profile) => {
if (err) {
res.json({ msg: 'Failded to add profiledetails' });
} else {
res.json({ msg: 'successfully add profile details' });
}
});
});
The data you insert is incomplete.
The properties in your schema marked as required: true need to be inserted aswell. Because you do not meet the schema requirements, it is failing.

Mongoose: How to findById when the query is not complete

I am building a search query where it will find a database object by its ID even while the user is typing.
ordersRouter.route('/searchorder/:term')
.get(function(req, res){
term = req.params.term;
console.log(term);
Orders.findById(term)
.populate({ path: 'userPurchased products.product', select: '-username -password' })
.exec(function(err, orders){
if (err) throw err;
res.json([orders]);
});
});
The problem here is that when the term does not exactly the same as the ID, it will return nothing. How can I return IDs with partial term?
EDIT: My order model schema
var orderSchema = new Schema({
orderId: { type: String },
userPurchased: { type: Schema.Types.ObjectId, ref: 'users' },
products: [
{
product: { type: Schema.Types.ObjectId, ref: 'products' },
size: { type: String, required: true },
quantity: { type: Number, required: true },
subTotal: { type: Number, required: true }
}
],
totalQuantity: { type: Number },
totalPrice: { type: Number },
modeOfPayment: { type: String },
shippingAd: { type: String },
refNumber: { type: String },
isDone: { type: Boolean, default: false },
orderStatus: { type: String, default: 'Pending' },
dateOrdered: { type: Date, default: Date.now() },
fromNow: { type: String }
});
You can run your query using regex i.e. create a regular expression object from the string using the RegExp constructor then run the query as:
ordersRouter.route('/searchorder/:term')
.get(function(req, res){
term = req.params.term;
console.log(term);
Orders.find({'orderId': new RegExp(term)})
.populate({
path: 'userPurchased products.product',
select: '-username -password'
})
.exec(function(err, orders){
if (err) throw err;
res.json(orders);
});
});

Resources