Node: Updating DB with PUT not working for MongoDB - node.js

My GET returns an array of 2 simple objects from the DB:
[
{
"_id": "60491b5741893d23216d2de3",
"text": "test`",
"score": 19,
"createdAt": "2021-03-10T19:17:43.809Z"
},
{
"_id": "604947c7b3a7ed28c43c05b7",
"text": "HELLO",
"score": 22,
"createdAt": "2021-03-10T22:27:19.739Z"
}
]
In Postman, I am trying to do a PUT to /604947c7b3a7ed28c43c05b7 to update that post. In the body, I am sending:
{
"text": "Updated post test",
"score": 100
}
and my node route looks like this:
router.put('/:id', async(req,res) => {
const posts = await loadPostsCollection();
const post = {};
if (req.body.text) post.text = req.body.text;
if (req.body.score) post.score = req.body.score;
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
);
res.status(200).send();
})
I am getting a success message back but when I do a GET to see the array, the value hasn't changed for that post.

I am assuming you're using the native mongodb node driver. In mongoose findOneAndUpdate() will not actually execute the query unless a callback function is passed. I don't know this with full certainly, but it sounds like the native driver works the same way. So you would have to rewrite your code like so:
posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true },
result => {console.log(result)}
);
Another way to do it is by appending .then() to the end, because according to the docs, it return a promise if no callback is passed. So here is how I would do it:
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
).then(r => r);

Related

mongoose query multiple operations in one request

I'm trying to use the $set, $addToSet and $inc at the same time for my report of sales and
tbh I'm not even sure if I did the right approach since it's not working.
once I send the request, the console gives me the error 404 but when I check the req.body the data was correct. so I was wondering if the problem is my query on mongoose because this was the first time I use multiple operations on mongoose query
export const report_of_sales = async (req, res) => {
const { id } = req.params;
console.log(req.body);
try {
if (!mongoose.Types.ObjectId.isValid(id)) return res.status(404).json({ message: 'Invalid ID' });
let i;
for (i = 0; i < req.body.sales_report.length; i++) {
await OwnerModels.findByIdAndUpdate(id, {
$inc: {
total_clients: req.body.total_clients,
total_product_sold: req.body.sales_report[i].qty,
sales_revenue: req.body.sales_report[i].amount
},
$set: {
"months.$[s].month_digit": req.body.months[i].month_digit,
"months.$[s].targetsales": req.body.months[i].targetsales,
"months.$[s].sales": req.body.months[i].sales,
},
$addToSet: {
sales_report: {
$each: [{
identifier: req.body.sales_report[i].identifier,
product_name: req.body.sales_report[i].product_name,
generic_name: req.body.sales_report[i].generic_name,
description: req.body.sales_report[i].description,
qty: req.body.sales_report[i].qty,
amount: req.body.sales_report[i].amount,
profit: req.body.sales_report[i].profit
}]
}
}
}, {
arrayFilters: [
{
"s.month_digit": req.body.months[i].month_digit
}
],
returnDocument: 'after',
safe: true,
}, { new: true, upsert: true })
}
} catch (error) {
res.status(404).json(error);
}
}
Well, you are looking at the body, but you are actually using query parameter named id. This is probably undefined, which leads to ObjectId.isValid(id) returning false.
You should decide on whether to pass this data as a query param or in the request body and adjust your code accordingly.

Firebase database collection returns empty array when trying to get all documents

I'm trying to get all documents from my database collection "posts" but I'm getting an empty array instead.
The strange thing is that I'm able to get all documents from another collection called "users" that has the same structure and using the exact same code.
I've spent days looking for an answer but I haven't been able to find the solution.
This is the request:
const db = admin.firestore();
exports.getAllPosts = (req, res) => {
db.collection('posts')
.orderBy('createdAt', 'desc')
.get()
.then(snapshot => {
let posts = [];
snapshot.forEach((doc) => {
posts.push({
id: doc.id,
body: doc.data().body,
author: doc.data().author,
createdAt: doc.data().timestamp,
voteScore: doc.data().voteScore
});
});
return res.json(posts);
})
.catch(err => {
console.error(err);
res.status(500).json({ error: err.code });
});
}
And this is the response:
[]
This is what my current collection looks like:
Posts collection screenshot
This the response that I get when I return "snapshot":
{
"_query": {
"_firestore": {
"_settings": {
"projectId": "readable-bf7a6",
"firebaseVersion": "9.6.0",
"libName": "gccl",
"libVersion": "4.10.0 fire/9.6.0"
},
"_settingsFrozen": true,
"_serializer": {
"allowUndefined": false
},
"_projectId": "readable-bf7a6",
"registeredListenersCount": 0,
"bulkWritersCount": 0,
"_backoffSettings": {
"initialDelayMs": 100,
"maxDelayMs": 60000,
"backoffFactor": 1.3
},
"_clientPool": {
"concurrentOperationLimit": 100,
"maxIdleClients": 1,
"activeClients": {},
"failedClients": {},
"terminated": false,
"terminateDeferred": {
"promise": {}
}
}
},
"_queryOptions": {
"parentPath": {
"segments": []
},
"collectionId": "posts",
"converter": {},
"allDescendants": false,
"fieldFilters": [],
"fieldOrders": [
{
"field": {
"segments": [
"createdAt"
]
},
"direction": "DESCENDING"
}
],
"kindless": false
},
"_serializer": {
"allowUndefined": false
},
"_allowUndefined": false
},
"_readTime": {
"_seconds": 1622395245,
"_nanoseconds": 513743000
},
"_size": 0,
"_materializedDocs": null,
"_materializedChanges": null
}
Notice how the request for the collection "users" works successfully:
const db = admin.firestore();
exports.getAllUsers = (req, res) => {
db.collection('users')
.orderBy('createdAt', 'desc')
.get()
.then(snapshot => {
let users = [];
snapshot.forEach((doc) => {
let users = [];
snapshot.forEach((doc) => {
users.push({
id: doc.data().userId,
email: doc.data().email,
handle: doc.data().handle
});
});
return res.json(users);
})
.catch(err => {
console.error(err);
res.status(500).json({ error: err.code });
});
}
And the response:
[
{
"id": "EPoHBxhQFUXbcL3TCVx1LdUG2nO2",
"email": "ruben#gmail.com"
},
{
"id": "RqEa3dEq8TSDcZYeolXafju67rB2",
"email": "user10#gmail.com"
},
{
"id": "dxveb4n2iMQej5Q14uprsKRxFp23",
"email": "user4#gmail.com",
"handle": "user4"
},
{
"id": "YQPzBPcsqlVZk9iJEuZTHKUNuVG2",
"email": "user2#gmail.com",
"handle": "user2"
},
{
"id": "CZ05BJxi3TUOpIrmBaz539OWlbC3",
"email": "user#gmail.com",
"handle": "user"
},
{
"id": "t0t83BVwt4gVgJkDv7HL1r1MaKr1",
"email": "userJose2#gmail.com",
"handle": "Jose"
}
]
This is what the users collection looks like in Firebase:
Users collection screenshot
Why is one collection failing when the other works fine and I'm using the same code? What am I missing here?
Thanks in advance and I hope I've made it as clear as possible. Please let me know if you need me to provide anything else.
Very simple my friend, your posts documents can't be ordered like this:
.orderBy('createdAt', 'desc')
Because the post documents does not have the createdAt property, but they have a timestamp property, you should use that property to order your posts like this:
.orderBy('timestamp', 'desc')
I hope that helps 👍
I am not answering directly to #Ruben Garcia Bri, but for future firebase developers who may run into the problem of getting empty documents, I also ran into the same problem, but I solved it by adding a field to the particular document I am trying to retrieve.
Sometimes the cause is because the documents you are trying to get have no field in them.
I mean that a document must have a field before the server can recognize it as an existing document.
So if you run into this problem, consider adding a field to that document before you can successfully retrieve it.

mongoose schema transform not invoked if document is returned directly from query

I have an endpoint that does an operation such as this:
const pipeline = [
{
$match: {
$and: [
{
$or: [...],
},
],
},
},
{
$group: {
_id : '$someProp',
anotherProp: { $push: '$$ROOT' },
},
},
{ $sort: { date: -1 } },
{ $limit: 10 },
]
const groupedDocs = await MyModel.aggregate(pipeline);
The idea here is that the returned documents look like this:
[
{
_id: 'some value',
anotherProp: [ /* ... array of documents where "someProp" === "some value" */ ],
},
{
_id: 'another value',
anotherProp: [ /* ... array of documents where "someProp" === "another value" */ ],
},
...
]
After getting these results, the endpoint responds with an array containing all the members of anotherProp, like this:
const response = groupedDocs.reduce((docs, group) => docs.concat(group.anotherProp), []);
res.status(200).json(response);
My problem is that the final documents in the response contain the _id field, but I want to rename that field to id. This question addresses this issue, and specifically this answer is what should work, but for some reason the transform function doesn't get invoked. To put it differently, I've tried doing this:
schema.set('toJSON', {
virtuals: true,
transform: function (doc, ret) {
console.log(`transforming toJSON for document ${doc._id}`);
delete ret._id;
},
});
schema.set('toObject', {
virtuals: true,
transform: function (doc, ret) {
console.log(`transforming toObject for document ${doc._id}`);
delete ret._id;
},
});
But the console.log statements are not executed, meaning that the transform function is not getting invoked. So I still get the _id in the response instead of id.
So my question is how can I get id instead of _id in this scenario?
Worth mentioning that toJSON and toObject are invoked (the console.logs show) in other places where I read properties from the documents. Like if I do:
const doc = await MyModel.findById('someId');
const name = doc.name;
res.status(200).json(doc);
The response contains id instead of _id. It's almost like the transform function is invoked once I do anything with the documents, but if I pass the documents directly as they arrive from the database, neither toJSON nor toObject is invoked.
Thanks in advance for your insights. :)
The toJSON and toObject methods won't work here because they don't apply to documents from an aggregation pipeline. Mongoose doesn't convert aggregation docs to mongoose docs, it returns the raw objects returned by the pipeline operation. I ultimately achieved this by adding pipeline stages to first add an id field with the same value as the _id field, then a second stage to remove the _id field. So essentially my pipeline became:
const pipeline = [
{
$match: {
$and: [
{
$or: [...],
},
],
},
},
// change the "_id" to "id"
{ $addFields: { id: '$_id' } },
{ $unset: ['_id'] },
{
$group: {
_id : '$someProp',
anotherProp: { $push: '$$ROOT' },
},
},
{ $sort: { date: -1 } },
{ $limit: 10 },
]
const groupedDocs = await MyModel.aggregate(pipeline);
It is possible to recast the raw objects into mongoose documents after getting them from the aggregate. You just need to transform them back one by one. They will then trigger the toJSON on return.
const document = Model.hydrate(rawObject);
Answer found here:
Cast plain object to mongoose document

mongoose find and return res.status.json

I'm developing an API that shows the bulletin boards list.
And it also shows one of the latest posts at the same time.
In controller I coded like this
exports.boards_get_all = (req, res, next)=>{
Board.find()
.exec()
.then(boards=>{
res.status(200).json({
count: boards.length,
boards: boards.map(board=>{
return {
board: {
id:board._id,
name: board.name,
order: board.order
},
post : Post.findOne({boardId:board._id})
.exec()
.then(posts=>{
console.log(posts)
return posts;
}
)
}
})
})
})
}
I use findOne() and try to return what Post found
but return post didn't work. just return empty value.
result :
{
"count": 1,
"boards": [
{
"board": {
"id": "5eb23f1fed38dc5dfc2debfd",
"name": "QnA board",
"order": 1
},
"post": {}
}
]
}
I think I took the wrong approach about using findOne()...
I want to get result like this
{
"count": 1,
"boards": [
{
"board": {
"id": "5eb23f1fed38dc5dfc2debfd",
"name": "QnA board",
"order": 1
},
"post": {
_id: 5eb364a27989ab6f414fcdb1,
userId: 5eb2aad669738d67b5497f3a,
boardId: 5eb23f1fed38dc5dfc2debfd,
type: 1,
title: 'this is a latest post in QnA board',
content: 'Sample content',
like: 0,
comment: 0,
view: 1,
date: 2020-05-07T01:30:10.957Z,
thumb: 'pic url',
__v: 0
}
}
]
}
The post is {} because the res.status(200).json() is executed and returned before the findOne callback returns a value.
To fix this, you need to get the post documents from the database before calling res.status(200).json().

updating multiple different array object elements in mongodb with nodejs

mongodb collection:
"_id": ObjectId("5e2ac528e9d99f3074f31de7"),
"publications": [
{
"_id": ObjectId("5e2ac528e9d99f3074f31de8"),
"name": "Times of India",
"productCode": "TCE1",
"tradeCopies": 40
},
{
"_id": ObjectId("5e2ac528e9d99f3074f31de9"),
"publicationName": "Economic Times",
"productCode": "ECE1",
"tradeCopies": 100
}
],
"orderCreatedBy": ObjectId("5e2977e1cc1208c65c00648b"),
"submittedTo": ObjectId("5e2555363405363bc4bf86c2"),
Nodejs Code
i would get multiple "productCode" like "TCE1","ECE1" etc,and i need to update tradeCopies of all the object array elements in one go according to their productCodes
Here is what i tried
exports.editOrder = async (req, res, next) => {
const { orderId, dealerId, productCode, tradeCopies } = req.body;
try{
const orders: await Order.updateOne(
{ _id: orderId,
submittedTo: dealerId,
"publications.productCode": productCode},
{$set:{"publications.$.tradeCopies":50}}
)
res.status(200).json({
orders,
message: "order submitted"
});
} catch (error) {
res.send(error);
}
};
CONCERNS
1-this query is updating only 1 array object element according to the matched productCode i want all the tradeCopies of all the array objects according to their productCodes to be updated in onego
2- the above query is working only in mongo Shell not in nodejs driver and whenever i remove double quotes in nodejs query vscode shows there might an error
You want to use arrayFilters.
const orders: await Order.updateOne(
{ _id: orderId,
submittedTo: dealerId,
"publications.productCode": productCode
},
{ $set: { "publications.$[element].tradeCopies":50 } },
{ arrayFilters: [ { "element.productCode": productCode } ] }
)
I'm not sure what you mean by removing the double quotes, but this snippet is nodejs driver compatible.

Resources