I want to pass array object inside array subdocument in mongoose - node.js

Here is my Schema
I am trying to add replies array inside answers array. If someone answers a question and if someone wants to reply on the given answer
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Schema;
const questionSchema = new mongoose.Schema(
{
postedBy: {
type: ObjectId,
required: true,
ref: "User",
},
question: {
type: String,
required: true,
},
photo: {
data: String,
required: false,
},
answers: [
{
userId: { type: ObjectId, ref: "User" },
answerType: {
data: String,
required: false,
},
answer: String,
replies: [
{
userId: { type: ObjectId, ref: "User" },
reply: String,
replyType: {
data: String,
required: false,
},
},
],
},
],
questionType: {
data: String,
required: false,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
},
},
{ timeStamps: true }
);
module.exports = mongoose.model("Question", questionSchema);
Here is my Controller method
exports.postReply = (req, res) => {
const reply = req.body.reply || "";
const userId = req.user._id || "";
const answerId = req.body.answerId || "";
Question.findByIdAndUpdate(
{ _id: answerId },
({ $push: { answers: { answer: { replies: { reply, userId } } } } },
{ new: true }),
(err, newReply) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
} else {
res.json({
msg: "Reply posted successfully",
newReply,
});
}
}
);
};
I feel I am going wrong on the findOneAndUpdate method. I am getting no error on the console but newReply comes null. Any help will be appreciated.

I would suggest you using the $addToSet instead of the $push operator as you are adding a document to the array. (see: https://docs.mongodb.com/manual/reference/operator/update/addToSet/).
If you want to add more than one document to the array, refer also to the $each operator together with $addToSet.
So your coding can look similiar to this (note: the variable 'yourDocument' is the document you want to add):
Question.findByIdAndUpdate(
{ _id: answerId },
{ $addToSet: { answers: yourDocument } },
{ new: true },
(err, newReply) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
} else {
res.json({
msg: "Reply posted successfully",
newReply,
});
}
}
);
};

The problem is clearly the parentesis around
({ $push: { answers: { answer: { replies: { reply, userId } } } } }, { new: true })
Doing this console.log( ({a:1}, {b:2}) ); will log {b: 2} which means you are doing this
Question.findByIdAndUpdate( { _id: answerId }, { new: true }, (err, newReply) => {
So remove the parentesis and you should be good
Question.findByIdAndUpdate(
{ _id: answerId },
{ $push: { answers: { answer: { replies: { reply, userId } } } } },
{ new: true },
(err, newReply) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
} else {
res.json({
msg: "Reply posted successfully",
newReply,
});
}
}
);

Related

problem when creating one to many data in mongoose

so i'm creating CRUD with relation of two collections, then i got problem, i can't do push from first collection data to second collection. this is my code.
Schema
const CourseSchema = new Schema(
{
title: {
type: String,
required: true,
},
desc: String,
price: Number,
video: String,
category: String,
status: Number,
lessons: [
{
type: Schema.Types.ObjectId,
ref: "Lessons",
},
],
},
{
timestamps: true,
}
);
const course = mongoose.model("Courses", CourseSchema);
const LessonSchema = new Schema(
{
title: {
type: String,
required: true,
},
desc: String,
video: String,
status: Number,
},
{ timestamps: true }
);
const Lessons = mongoose.model("Lessons", LessonSchema);
code to do data push
Lessons.create(req.body)
.then((data) => {
res.status(200).send({
status: 200,
message: "Successfully Create Lessons",
data: data,
});
Course.findByIdAndUpdate(
courseId,
{ $push: { lessons: data._id } },
{ safe: true, upsert: true, new: true }
);
})
is there any solution for my problem? please help me, i'm just learning about one to many relation in nodejs using mongoose
You need a callback function to make it works.
Lessons.create(req.body)
.then((data) => {
res.status(200).send({
status: 200,
message: "Successfully Create Lessons",
data: data,
});
course.findByIdAndUpdate(
courseId,
{ $push: { lessons: data } },
{ safe: true, upsert: true, new: true },
function (err, newdoc) { // callback function
if (err) {
console.log(err);
} else {
console.log("completed");
}
}
);
})

how to find in an Array of Objects by id?

I want to use mongoose to find in an array of objects by id.
I have this list:
{
"data":[
{
"_id":"60ce0ea7eb945a22288fd0ba",
"parent":"50ce0e44eb945a22288fd0b1",
"label":"label 1-2",
"ancestors":[
{
"_id":"50ce0e44eb945a22288fd0b1",
"label":"label 1-1"
},
{
"_id":"40ce077e90c6262bdc21aa44",
"label":"label 1"
}
]
},
{
"_id":"50ce0e44eb945a22288fd0b1",
"parent":"60ce077e90c6262bdc21aa55",
"label":"label 1-1",
"ancestors":[
{
"_id":"40ce077e90c6262bdc21aa44",
"label":"label 1"
}
]
},
{
"_id":"40ce077e90c6262bdc21aa44",
"parent":null,
"label":"label 1",
"ancestors":[]
}
]
}
This is the schema:
const categorySchema = new mongoose.Schema(
{
label: {
type: String,
required: true
},
parent: {
type: ObjectId,
default: null,
ref: 'category'
},
ancestors: [
{
_id: {
type: ObjectId,
ref: 'category'
},
label: String
}
]
},
{ timestamps: true }
);
I tried to do this:
async getDescendants(req, res) {
let { pId } = req.body;
if (!pId) {
return res.json({ error: 'All filled must be required' });
} else {
try {
const data = await patternModel
.find({ 'ancestors._id': pId })
.select({
_id: false,
label: true
})
.exec();
if (data) {
return res.json({ data });
}
} catch (err) {
return res.json({ err: err });
}
}
}
this is my actual result:
{
"data": []
}
when I change .find({ 'ancestors._id': pId }) to .find({ 'ancestors.label': label }) it works but not for the id.
It is not a simple field. It is an array of subdocuments. Use elemMatch.
Edit: When querying _id fields you will have to wrap convert them into ObjectIds (specific to Mongo).
let newPid = mongoose.Types.ObjectId(pId);
const data = await patternModel.find({ ancestors: { $elemMatch : { _id: newPid} } })
.select({ _id: false,label: true })
.exec();

updating document inside array in mongoose

I want to update my answer object inside answers array. I am using following schema
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Schema;
const questionSchema = new mongoose.Schema(
{
postedBy: {
type: ObjectId,
required: true,
ref: "User",
},
question: {
type: String,
required: true,
},
photo: {
data: String,
required: false,
},
answers: [
{
userId: { type: ObjectId, ref: "User" },
answer: String,
},
],
questionType: {
data: String,
required: false,
},
},
{ timeStamps: true }
);
module.exports = mongoose.model("Question", questionSchema);
I am using updateOne method to update my answer in my db. Can anyone explain what is missing here. I am been trying to solve this since hours
exports.updateAnswer = (req, res) => {
const questionId = req.body.questionId;
const answerId = req.body.answerId;
Question.findOne({ _id: questionId }).exec((err, question) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
return;
}
if (!question) {
res.status(400).json({
error: "question not found",
});
return;
}
});
Question.updateOne(
{ _id: answerId },
{
$set: {
"answers.$.answer": "This is update answer. My name is Ravi Dubey",
},
},
{ new: true },
(err, success) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
}
res.json({
msg: "answer updated successfully",
success,
});
}
);
};
My result is coming successful but answer is not updating in db.
I am confused on Question.updateOne method.
Any help appreciated.
If you trying to query based on id of one of the documents in the answers array then instead of {_id: answerId} you need to provide {'answers._id': answerId}. And also if you need the updated document as result then you should use the findOneAndUpdate method.
Question.findOneAndUpdate(
{ "answers._id": answerId },
{ $set: { "answers.$.answer": "some answer" } },
{ new: true },
(err, data) => {
// handle response
}
);

Cast to ObjectId failed for value at path for model error

This is my Profile Schema:
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
// Special field type because
// it will be associated to different user
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
company: {
type: String,
},
website: {
type: String,
},
location: {
type: String,
},
status: {
type: String,
required: true,
},
skills: {
type: [String],
required: true,
},
bio: {
type: String,
},
githubusername: {
type: String,
},
experience: [
{
title: {
type: String,
required: true,
},
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
education: [
{
school: {
type: String,
required: true,
},
degree: {
type: String,
required: true,
},
fieldofstudy: {
type: String,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
social: {
youtube: {
type: String,
},
twitter: {
type: String,
},
facebook: {
type: String,
},
linkedin: {
type: String,
},
instagram: {
type: String,
},
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = Profile = mongoose.model('profile', ProfileSchema);
This is my view api. It doesn't work. it only return Cast to ObjectId failed for value { 'experience._id': '5edcb6933c0bb75b3c90a263' } at path _id for model profile
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findById({
'experience._id': req.params.viewexp_id,
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
How can I fix this? I tried looking at the stackoverflow of the same errors. still it doesn't seem to work.
and this is what I am trying to hit
The problem is that you have to convert your string _id to mongoose object id using this function mongoose.Types.ObjectId and my suggestion is to use findOne function instead of findById,
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
let id = mongoose.Types.ObjectId(req.params.viewexp_id);
const exp = await Profile.findOne(
{ "experience._id": req.params.viewexp_id },
// This will show your sub record only and exclude parent _id
{ "experience.$": 1, "_id": 0 }
);
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findOne({
'experience._id': mongoose.Types.ObjectId(req.params.viewexp_id),
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
You are saving object id . but your param id is string. convert it in ObjectId. Please check my solution.
router.post(
"/",
[
auth,
[
check("status", "status is required").not().isEmpty(),
check("skills", "skills is required").not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
company,
website,
location,
bio,
status,
githubuername,
skills,
youtube,
facebook,
twitter,
instagram,
linkedin,
} = req.body;
const profileFileds = {};
profileFileds.user = req.user.id;
if (company) profileFileds.company = company;
if (website) profileFileds.website = website;
if (location) profileFileds.location = location;
if (bio) profileFileds.bio = bio;
if (status) profileFileds.status = status;
if (githubuername) profileFileds.githubuername = githubuername;
if (skills) {
profileFileds.skills = skills.split(",").map((skill) => skill.trim());
}
//Build profile object
profileFileds.social = {};
if (youtube) profileFileds.social.youtube = youtube;
if (twitter) profileFileds.social.twitter = twitter;
if (facebook) profileFileds.social.facebook = facebook;
if (linkedin) profileFileds.social.linkedin = linkedin;
if (instagram) profileFileds.social.instagram = instagram;
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//update
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFileds },
{ new: true }
);
return res.json(profile);
}
//Create profile
profile = new Profile(profileFileds);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("server Error");
}
}
);

findOneAndUpdate seems to be doubling my $inc count

I'm trying to post a comment on to my posts for my MERN app but I'm running into an issue where the comment (Posts.findOneAndUpdate) seems to posting the comments twice. I read a few posts on SO that described the issue to be the way mongoose handles queries but I must be missing something.
If anyone could explain what I'm doing wrong I would greatly appreciate it!
Route I'm using:
router.post('/newReply/:id', async function(req, res) {
const body = req.body
if (!body) {
return res.status(400).json({
success: false,
error: 'No text entered!',
})
}
const reply = new Replies(body)
if (!reply) {
return res.status(400).json({ success: false, error: err })
}
await Posts.findOneAndUpdate(
{ _id: req.params.id },
{
"$inc": { "replies": 1 },
"$push": { "comments": reply },
},
{
new: true
},
(err) => {
if (err) {
return res.status(404).json({
success: false,
error: err,
message: 'Post not found!',
})
}
return res.status(200).json({
success: true,
id: reply._id,
message: 'Reply created!',
reply: reply.reply,
points: reply.points,
createdAt: reply.createdAt
})
})
.catch(err => console.log(err))
})
Posts Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const PostsSchema = new Schema({
post: {
type: String,
required: true
},
points: {
type: Number,
default: 0
},
voters: {
type: Array
},
upvotedBy: {
type: Array
},
downvotedBy: {
type: Array
},
createdAt: {
type: Date,
default: Date.now
},
replies: {
type: Number,
default: 0
},
comments: {
type: Array
},
user_id: {
type: 'string'
},
deleted: {
type: Boolean,
default: false
}
});
module.exports = Posts = mongoose.model("posts", PostsSchema);

Resources