findOne mongoose query is not working properly - javascript

I have used express to create this web-app.
I also have mongoose model:
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
notes: [{
name: { type: String, required: true }
}]
}
When I try to find object inside of array(Notes) ->
modelName.findOne({ "notes:" {$elemMatch: {"_id": req.body.id } }})
.exec()
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
});
I get whole model object of one user instead of one note.
Here is the result which I get:
{
"_id": "5c51dd8be279e9016716e5b9",
"username": "user1",
"password": "",
"notes": [
{
"_id": "5c51dd8be279e901671gagag",
"name": "name of note1"
},
{
"_id": "5c51ff8be279e901671gagag",
"name": "name of note"
},
{
"_id": "5c51dd8be2131501671gagag",
"name": "name of note"
}
]
}
My expectation, however, is to receive something like this:
{
"_id": "5c51dd8be279e901671gagag",
"name": "name of note1"
}
P.S: It is not duplicate of this answer Mongoose Mongodb querying an array of objects. I have already tried to use code from that question, but it doesn't solve my problem

findOne() is working just fine. findOne() returns any document that matches the specified query, not part of a document. If you want just part of that document, you will have to get it in two parts...
modelName.findOne({ "notes": {$elemMatch: {"_id": req.body.id } }})
.exec()
.then(result => {
// Will get an array of notes whose _id === req.body.id
const resNote = result.notes.filter(n => n._id === req.body.id);
console.log(resNote);
})
.catch(err => {
console.log(err);
});
See the documentation here. If you note, it mentions that the function " finds one document".

Related

How can I count all category under productId?

So I'm still new using MongoDB, so what I'm trying to do here is count all category under productId who have same category. So the expected output should be 7. I used populate first but got stuck on how can I use the $count. Instead I use aggregate and then use $lookup, but i only empty array of product
CartSchema.js
const CartSchema = new mongoose.Schema({
productId: {type: mongoose.Schema.Types.ObjectId, ref: 'Product'}
})
export default mongoose.model('Cart', CartSchema)
ProductSchema.js
const ProductSchema = new mongoose.Schema({
category: {type: String, required: true},
})
export default mongoose.model('Product', ProductSchema)
I used this code to show the information under productId.
router.get('/categories', async (req, res) => {
try {
const cart = await Cart.find()
.populate([
{path: 'productId', select: 'category' },
]).exec()
res.status(200).json(cart);
} catch (error) {
res.status(500).json({error: error.message})
}
})
The result of populate method.
[
{
"_id": "63b410fdde61a124ffd95a51",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b41a679950cb7c5293bf12",
"productId": {
"_id": "63b41637e3957a541eb59e81",
"category": "CASE"
},
},
{
"_id": "63b433ef226742ae6b30b991",
"productId": {
"_id": "63b41637e3957a541eb59e81",
"category": "CASE"
},
},
{
"_id": "63b670dc62b0f91ee4f8fbd9",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b6710b62b0f91ee4f8fc13",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b671bc62b0f91ee4f8fc49",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b6721c62b0f91ee4f8fcc5",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
]
So I used this method, but instead, I just get an empty array
router.get('/categories', async (req, res) => {
try {
const cart = await Cart.aggregate([
{
$lookup: {
from: 'product',
localField: 'productId',
foreignField: '_id',
as: 'product'
}
},
{
$unwind: "$product"
},
{
$group: {
_id: "$product.category",
total: {
$sum: 1
}
}
},
{
$sort: {total: -1}
},
{
$project: {
_id: 0,
category: "$_id",
total: 1
}
},
])
res.status(200).json(cart);
} catch (error) {
res.status(500).json({error: error.message})
}
})
In the aggregation, the collection to perform the $lookup on should be products (with an s) rather than product.
The name of the collection that Mongoose creates in your database is the same as the name of your model, except lowercase and pluralized, as documented in the documentation.
Mongoose automatically looks for the plural, lowercased version of your model name. Thus, for the example above, the model Tank is for the tanks collection in the database.
(emphasis theirs)
When using the aggregation framework, your aggregation pipeline is sent to the database as-is. Mongoose doesn't do any sort of coercion or casting on it. So when writing aggregation pipelines you should more or less forget you're using Mongoose. What's important is the name of the underlying collection in Mongo, which is generated from your model name based on the mentioned rule.
You can also override the collection name yourself if desired, for example:
export default mongoose.model('Product', ProductSchema, 'xyz');
This will override Mongoose's default naming behavior and will name the collection xyz.

How to update nested object in mongoose? [duplicate]

This question already has an answer here:
How to update nested object in array (Mongoose/MongoDB)
(1 answer)
Closed 6 months ago.
I'm building a kanban task management app. So far I've been able to create a task and delete task but I can get my head around how to update a nested task object. I have searched the documentation there's no clear explanation.
I wanted to update the task with the id of 62ff74bfe80b11ade2d34455 - req.params.id with the req.body.
{
"_id": "62fa5aa25778ec97bc6ee231",
"user": "62f0eb5ebebd0f236abcaf9d",
"name": "Marketing Plan",
"columns": [
{
"name": "todo",
"_id": "62fa5aa25778ec97bc6ee233",
"tasks": [
{
"title": "Task Four",
"description": "This is task four",
"subtasks": [
{
"name": "wash dshes",
"completed": false,
"_id": "62ff74bfe80b11ade2d34456"
},
{
"name": "do homework",
"completed": false,
"_id": "62ff74bfe80b11ade2d34457"
}
],
"_id": "62ff74bfe80b11ade2d34455"
}
]
},
{
"name": "doing",
"_id": "62fa5aa25778ec97bc6ee234",
"tasks": []
},
{
"name": "done",
"_id": "62fa5aa25778ec97bc6ee235",
"tasks": []
}
],
"__v": 0
}
// #desc Edit task
// #route PUT /api/tasks/:id
// #access Private
const editTask = asyncHandler(async (req, res) => {
const { title, description, subtasks, status } = req.body;
const task = await Board.findOne({ "columns.tasks._id": req.params.id });
const taskStatus = await Board.findOne({
"columns.tasks._id": req.params.id,
"columns._id": status,
});
if (!task) {
res.status(400);
throw new Error("Task not found");
}
// Check for user
if (!req.user) {
res.status(401);
throw new Error("User not found");
}
// Make sure the logged in user matches the task user
if (task.user.toString() !== req.user.id) {
res.status(401);
throw new Error("User not authorized");
}
if (taskStatus) {
const updatedTask = await Board.findOneAndUpdate({
"columns.tasks._id": req.params.id,
});
}
task.columns.map(async (val) => {
// check if tasks id equals the status id in the request body
if (val._id.toString() === status) {
const updatedTask = await Board.findOneAndUpdate(
{
"columns.tasks._id": req.params.id,
},
{ $set: { "columns.$[].tasks": { _id: req.params.id } } },
{ new: true }
);
}
});
});
I had to do something similar recently and I couldn't find a way to update it using mongoose. So I have come up with a workaround.
After doing all your checks
// retrieve the column index you want to update (I assume you have only these 3 columns)
const columnIndex = _.findIndex(task.columns, { name: "todo" });
// retrieve the actual column object
const column = task.columns[columnIndex ];
// get the index of the specific task you want to update
const taskIndex = _.findIndex(column.tasks, { _id: req.params.id });
// manipulate whatever you want here on the specific task by its index
task.columns[columnIndex].tasks[taskIndex]._id = req.params.id
// then finally you need to update the whole columns array
await Board.updateOne({your query here}, {
$set: {
columns: task.columns
}
});
This is actually dirty, but your data is structured in a very difficult way.
I am using lodash in the example. you are free to use JavaScript's array methods

Mongoose: res.json returns array with null records when "toObject" is used. Why and how can I solve this?

I'm currently trying to setup an API where I can fetch an array of objects that are assigned to a userId. I have defined the following Mongoose Model:
const userSchema = mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, trim: true },
password: { type: String, required: true, minlength: 6 },
engagements: [
{ type: mongoose.Types.ObjectId, required: true, ref: "Engagement" },
],
});
And have setup a controller like this:
exports.getEngsByUid = async (req, res, next) => {
const uid = req.params.uid;
let uidWithEngs;
try {
uidWithEngs = await User.findById(uid).populate("engagements");
} catch (err) {
const error = new HttpError("Fetching engagements failed!", 500);
return next(error);
}
if (!uidWithEngs || uidWithEngs.engagements.length === 0) {
return next(new HttpError("Could not find engagements for this user", 500));
}
res.json({
engagements: uidWithEngs.engagements.map((e) => {
e.toObject();
}),
});
};
The problem I am currently facing, is that when I send a GET requests to my defined path, I fetch an array of 'engagements' with null values:
{
"engagements": [
null,
null
]
}
However, if I remove the toObject method (and required .map method) and setup the res as follows:
res.json({
engagements: uidWithEngs.engagements,
});
I do get my array:
{
"engagements": [
{
"_id": "5fb47975eb319d24c098a06d",
"title": "testTitle1",
"client": "testClient1",
"year": 2030,
"owner": "5fb4795deb319d24c098a06b",
"__v": 0
},
{
"_id": "5fb4698aeb319d24c098a06f",
"title": "testTitle2",
"client": "testclient2",
"year": 2040,
"owner": "5fb4795deb319d24c098a06b",
"__v": 0
}
]
}
Why does the use of toObject() return an array with 2 null records and when I don't use the toObject() method, it returns 2 complete records? How can I solve this?
Thank you|
change res.json(...) to basically the short hand for arrow functions and it will work.

Ideal way to store multiple texts in mongoose with Node JS?

I've been building a mongoose schema for texts that will be displayed across different pages, and it has end point to POST data for updating the texts.
For example, I would like to store text messages that will be displayed/updated in About Page and Contact Page
What would be the preferred way of designing the text model?
1) Model that has all messages stored in one data object
In front-end, the parent component fetches all text messages with Texts.findOne() and trickles down to pages that need it
const textsSchema = new Schema(
{
aboutMessage1: {
type: String,
required: true
},
aboutMessage2: {
type: String,
required: true
},
contactMessage1: {
type: String
},
contactMessage2: {
type: String
}
},
{ timestamps: true }
);
2) Model that contains each message--so it will have multiple objects
In fron-end, each page uses Text.findById(textId) to retrieve each message
const textSchema = new Schema(
{
// Example: name = contactMessage
name: {
type: String
},
message: {
type: String
}
},
{ timestamps: true }
);
3) Multiple models that contains texts for each page
Similar to 1) approach, texts get fetched with Texts.findOne(), but performed in each page
const aboutTextsSchema = new Schema(
{
message1: {
type: String,
required: true
},
message2: {
type: String,
required: true
},
},
{ timestamps: true }
);
const contactTextsSchema = new Schema(
{
message1: {
type: String,
},
message2: {
type: String,
},
},
{ timestamps: true }
);
The most promising option is the second one. Because first and third options are static, and if in the future, you need to add a new page or or a new message to an existing page, it will require changes in the mongoose model, and deployment for API.
But I think, instead of creating a text schema, it would better to create a page schema for your scenario.
Here I embed messages inside the page schema.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const pageSchema = new Schema(
{
page: {
type: String
},
messages: [
new Schema({
name: {
type: String
},
message: {
type: String
}
})
]
},
{ timestamps: true }
);
module.exports = mongoose.model("Page", pageSchema);
Now we can use this post route to create a page:
router.post("/pages", async (req, res) => {
const result = await Text.create(req.body);
res.send(result);
});
We can create a page and its messages using the previous post route.
Request Body:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:39:05.154Z",
"__v": 0
}
Response:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:39:05.154Z",
"__v": 0
}
If later we want to add a message to a page we can use the following put route.
router.put("/pages/:id", async (req, res) => {
const result = await Page.findByIdAndUpdate(
req.params.id,
{
$push: { messages: req.body }
},
{ new: true }
);
res.send(result);
});
Request Body:
{
"name": "Abou3",
"message": "About3 message..."
}
Response:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
},
{
"_id": "5e493926f905ab3300106f94",
"name": "Abou3",
"message": "About3 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:44:22.763Z",
"__v": 0
}
When client needs a page's messages, all we need to do is retrieving the page by it's id or page name:
router.get("/pages/id/:id", async (req, res) => {
const result = await Page.findById(req.params.id);
res.send(result);
});
//or
router.get("/pages/name/:name", async (req, res) => {
const result = await Page.findOne({ page: req.params.name });
res.send(result);
});

Update subarray of objects in mongodb

I have this document in my database:
{
"_id": "ObjectId(...)",
"chapters": [
{
"_id": "ObjectId(...)",
"link": "128371.html",
"content": ""
}
]
}
The chapters array can have up to 3k items, and I have to populate each content attribute with some info. I want to be able to save the info I want inside the right object. Until now I was able to change the content attribute generally (in all items), but I am having trouble filtering it. This is what I managed to code using what I found in other questions:
let content = "Testing";
await models.ListNovel.updateOne(
{ link: novel_link },
{ $set: { "chapters.$[].content": content } }
);
I saw that { arrayFilters: [{ link: { $eq: chapter_link } }], multi: false } may work in some cases, but I don't use the link identifier in the update.
Thank you!
UPDATE
Similar to Suleyman's solution, I ended up with the following working code, I hope it may be useful for you.
await models.ListNovel.updateOne(
{ link: novel.link },
{ $set: { "chapters.$[elem].content": content } },
{
multi: true,
arrayFilters: [{ "elem.link": { $eq: chapter.link } }]
}
);
The condition in updateOne must match parent object, but you are using { link: novel_link } which belongs to the inner array object field, so it cannot find the document, and update doesn't happen.
To illustrate this, let's say your schema is like this:
const mongoose = require("mongoose");
const schema = new mongoose.Schema({
name: String,
chapters: [
new mongoose.Schema({
link: String,
content: String
})
]
});
module.exports = mongoose.model("ListNovel", schema);
Let's have this existing document in this collection:
{
"_id": "5e498a1fe21eea0e10690e39",
"name": "Novel1",
"chapters": [
{
"_id": "5e498a1fe21eea0e10690e3b",
"link": "128371.html",
"content": ""
},
{
"_id": "5e498a1fe21eea0e10690e3a",
"link": "222222.html",
"content": ""
}
],
"__v": 0
}
If we want to update this document's chapter with "link": "128371.html", first we need to find it with name or _id field, and update it using the filtered positional operator $.
router.put("/novels/:name", async (req, res) => {
const novel_link = "128371.html";
const content = "Testing";
const result = await ListNovel.findOneAndUpdate(
{ name: req.params.name },
{
$set: { "chapters.$[chapter].content": content }
},
{
arrayFilters: [{ "chapter.link": novel_link }],
new: true
}
);
res.send(result);
});
Here I used findOneAndUpdate to immediately retrieve the updated document, but you can also use the updateOne instead of findOneAndUpdate.
The result will be like this:
{
"_id": "5e498a1fe21eea0e10690e39",
"name": "Novel1",
"chapters": [
{
"_id": "5e498a1fe21eea0e10690e3b",
"link": "128371.html",
"content": "Testing" // => UPDATED
},
{
"_id": "5e498a1fe21eea0e10690e3a",
"link": "222222.html",
"content": ""
}
],
"__v": 0
}

Categories

Resources