NodeJS : Mongoose findByIdAndUpdate() returns null - javascript

Consider the following code on NodeJS for updating a Document By id:
router.put('/:orderId', async(req, res) => {
let update_orderId = req.params.orderId;
let new_item = req.body.item;
let new_item_desc = req.body.item_desc;
let new_quantity = req.body.quantity;
let new_unit_price = req.body.unit_price;
let new_total_cost = new_quantity * new_unit_price;
let new_status = req.body.status;
let new_priority = req.body.priority;
await RequestPermissionOrderSchema.findByIdAndUpdate( update_orderId, {
$set: {
item: new_item,
item_desc: new_item_desc,
quantity: new_quantity,
unit_price: new_unit_price,
total_cost: new_total_cost,
status: new_status,
priority: new_priority,
directOrder: false
}
},
{ new: true, useFindAndModify: false}, function (err, docs) {
if (err) {
console.log(err);
res.send(err);
} else {
console.log("Updated Request Permission Order : ", docs);
res.json(docs);
}
});
});
Everything is according to the latest documentation of Mongoose 5.10.9 and the schema also contains all the properties specified under $set: { ... } . But when I execute, I get the output as follows in the console:
updated Request Permission Order : null
I also make sure of whether the _id is available in the mongoDB database and that object ID is there. I can't seem to find what I did wrong. Really appreciate any help on this !
Also, note that I want to update by the _id auto-generated by MongoDB with each document.

Just pass the JSON object, remove the $set
await RequestPermissionOrderSchema.findByIdAndUpdate( update_orderId, {
$set: { // REMOVE THIS $set
item: new_item,
item_desc: new_item_desc,
quantity: new_quantity,
unit_price: new_unit_price,
total_cost: new_total_cost,
status: new_status,
priority: new_priority,
directOrder: false
} // REMOVE THIS ALSO
},
Only the JSON object.

Related

backend node js with mongoDB, patch request does not update date type value

I use node.js to build the backend and persist the data in MongoDB. When I do a patch request, I can change the values of all the fields of other types except for the one of date type.
This is the backend code for the patch request.
router.patch('/:id', isLoggedIn, async (req, res) => {
try {
const updatedBooking = await Booking.updateOne(
{_id: req.params.id},
{
$set: {userEmail: req.body.userEmail},
$set: {shiftDate: req.body.shiftDate},
$set: {isMorningShift: req.body.isMorningShift}
}
);
res.json(updatedBooking);
} catch (err) {
res.send({message: err});
}
});
This is the database scheme:
const BookingSchema=mongoose.Schema({
userEmail:{
type:String,
required:true
},
shiftDate:{
type:Date,
required:true
},
isMorningShift:{
type: Boolean,
required: true
}
});
The objects in MongoDB look like this:
{
"_id": "61787183e67b6822180175f9",
"userEmail": "admin2#parantion.nl",
"isMorningShift": false,
"__v": 0,
"shiftDate": "2066-06-23T00:00:00.000Z"
}
What might be the problem?
Instead of multiple $set, update all the keys in one,
const updatedBooking = await Booking.updateOne(
{_id: req.params.id},
{
$set: {
userEmail: req.body.userEmail,
shiftDate: new Date(req.body.shiftDate),
isMorningShift: req.body.isMorningShift
}
}
);
#fractal397's answer will work fine. If you want a more cleaner code, you can use this.
const bookingId = req.params.id;
const payload =
userEmail: req.body.userEmail,
shiftDate: new Date(req.body.shiftDate),
isMorningShift: req.body.isMorningShift
}
const booking = await Booking.findByIdAndUpdate(bookingId, payload);
P.S. - After Mongoose 4.0, new value for findByIdAndUpdate has been changed to false by default. So in this operation, data will be updated in the database but it will return the old value booking. To get updated value in response too, you will have to do -
const booking = await Booking.findByIdAndUpdate(bookingId, payload, { new : true });
Change the line:
$set: {shiftDate: req.body.shiftDate}
to
$set: {shiftDate: new Date(req.body.shiftDate)}
or
$set: {shiftDate: new Date()} //for todays date in your local format
This works:
I tested this with express like so:
app.get('/updateOne', async (req, res) => {
//get data through query params in url
const id = req.query.id;
const date = req.query.date;
//connect to db and collection
//1 connect
//2 set db and collection
const client = await MongoClient.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const collection = client.db("sample_airbnb").collection("listingsAndReviews");
//update function for date field
try {
const updatedBooking = await collection.updateOne(
{_id: id},
{
$set: {name: new Date(date)} //2066-06-23T00:00:00.000Z
}
);
res.json(updatedBooking);
} catch (err) {
res.send({'message': err});
}
})
Response:
{
"acknowledged": true,
"modifiedCount": 1,
"upsertedId": null,
"upsertedCount": 0,
"matchedCount": 1
}
And updated data in Mongoscloud:
_id
:
"100009690"
name
:
2066-06-23T00:00:00.000+00:00
The I called the endpoint like so:
http://localhost:5000/updateOne?id=100009690&date=2066-06-23T00:00:00.000Z and you see it's the same date format you say you expect.
Can you update your OP and show us the exact format you are passing in?? DO a console.log(req.body.shiftDate) on line 7 just before you pass it. I suspect here is where the issue is.
Obviously I shouldn't add dates to names field but this is purely for a quick test.
If updating multiple fields I'd with:
//update function
try {
const updatedBooking = await collection.updateOne(
{_id: id},
{
$set: {
name: name,
email: email,
lastShift: new Date(date)
}
}
);
res.json(updatedBooking);
} catch (err) {
res.send({'message': err});
}

How do I merge into an existing unique id in MongoDB with NodeJS?

I want to sum a documents field when I insert a new entry that has a duplicate unique id. This is what I have so far:
MongoClient.connect(process.env.MONGODB_URI || process.env.DB_CONNECTION, { useUnifiedTopology: true, useNewUrlParser: true }, function (err, db) {
if (err) throw err;
const dbo = db.db("mydb");
const messageTable = dbo.collection("comments");
for(var key in words){
let myobj =
[{
_id: key,
frequency: words[key]
}];
messageTable.insertMany(myobj, function(err, res){
if (err) throw err;
console.log("documents inserted");
});
messageTable.aggregate([
{ $match: { _id: key } },
{
$group: {
_id: key,
total: {
$sum: "$frequency"
}
}
},
{ $merge: { into: "comments", whenMatched: "replace" } }
]);
}
})
From reading the docs I tried to use the aggregate method to merge new entries if the key already exists as a unique id. This is not currently working. How would I merge duplicate documents in my MongoDB using NodeJS?
I have figured out a way to do this by reading the docs and with the help of a reddit user. If anyone wants to do something similar this is the easiest method:
messageTable.findOneAndUpdate({ "_id" : key },{$set: { "_id" : key}, $inc : { "frequency" : words[key] }},{upsert: true}).then(
console.log("document updated or inserted")
)
With this method the first argument is your query, the second changes the first match which in my case increments the frequency field, and the third are options for the method. The upsert option has a default value of false but when true creates a new document if one does not already exist. If you wish to read more you can find info here

Firebase Firestore can't save document

Summary
I have a node server that is getting a document from MongoDB and then saving it to Firestore. I have Firestore setup properly and I can save variables I create in node as a document but I can't save the document I get back from MongoDB. Does anyone know how to solve this problem? Your help would be greatly appreciated!
Error
"Value for argument "data" is not a valid Firestore document. Couldn't serialize object of type "model" (found in field event.data). Firestore doesn't support JavaScript objects with custom prototypes (i.e. objects that were created via the "new" operator)."
Code
Get stats from MongoDB & send to firebase
const objectFromMongoDb = await InstantComp.findOneAndUpdate(
{
_id: compId,
"competitors.userId": userObj._id,
inProgress: true
},
{ $set: { "competitors.$.stats": lifeStats } },
{ new: true }
);
addToFirestoreFunction(userObj._id, "NEW_COMP_DATA", objectFromMongoDb);
addToFirestoreFunction
async function addToFirestoreFunction(userId, eventType, data) {
try {
//read database
//if userId contains eventType singleEntry then remove from database
const timeStamp = new Date();
userId = userId.toString();
const userDoc = db.collection("pushData").doc(userId);
const pushData = await userDoc.set(
{
event: {
eventType,
data,
timeStamp
}
},
{ merge: true }
);
console.log("Document set in FireStore", pushData);
} catch (err) {
console.log("errpr pushing to firebase", err);
}
}
Object from MongoDB
This is the object returned from MongoDB which I copied from a console log.
{
inProgress: true,
_id: "5d63ffd3c364351ce81a230b", //an objectId from MongoDB
userId: "5cca01eb8c481129d711f479", //an objectId from MongoDB
compName: "Benji's Daily MuckShow",
competitors: [
{
inProgress: true,
_id: "5d63ffd3c364351ce81a230c", //an objectId from MongoDB
userId: "5cd65533c2e8700024309e6d", //an objectId from MongoDB
startingLifeTimeStats: [{kills: 0, wins: 10}],
stats: [{kills: 0, wins: 10}]
}
]
};``
async function addToFirestoreFunction(userId, eventType, data) {
try {
//read database
//if userId contains eventType singleEntry then remove from database
// Convert mongoose object to normal json object
data = JSON.parse(JSON.stringify(data));
const timeStamp = new Date();
userId = userId.toString();
const userDoc = db.collection("pushData").doc(userId);
const pushData = await userDoc.set(
{
event: {
eventType,
data,
timeStamp
}
},
{ merge: true }
);
console.log("Document set in FireStore", pushData);
} catch (err) {
console.log("errpr pushing to firebase", err);
}
}

MongoDB: remove skill from user document

So I have an issue. I have a MongoDB user document and this document has a property skills, which is an array filled with objects.
What I want to do now is the following. The client sends a request to delete one of these skills. So I send the skill ID and then I want to remove the skill from the user. How could I do this?
What I currently have: every item in the skills property array has a skill property which is an objectID. When this objectID matches the objectId that the client sent, then we want to remove this skill from the user
const removeSkill = async (req, res) => {
try {
const { userId, params: { skillId } } = req;
const user = await User.findByIdAndUpdate({ _id: userId }, {
$pull: { "skills.skill": skillId }
}, {
new: true
});
return res.status(200).json({
message: 'succesfully removed skill',
user
});
} catch (err) {
return sendErr(res, err);
}
};
What the user mongodb document looks like
The error I get
:no_entry: Error:
MongoError: Cannot use the part (skill) of (skills.skill) to traverse the element ({skills: [ { _id: ObjectId('5c8729be12e1cc05c04ea182'), name: "javascript", skill: ObjectId('5c8729be
12e1cc05c04ea181'), points: 22 }, { _id: ObjectId('5c8729dc12e1cc05c04ea184'), name: "node.js", skill: ObjectId('5c8729dc12e1cc05c04ea183'), points: 14 }, { _id: ObjectId('5c872a6c12e1c
c05c04ea186'), name: "css", skill: ObjectId('5c872a6c12e
First of all you shouldn't be using 2 Object ID in one object. The beauty of MongoDB is auto generated ObjectID. For your question (using skill ObjectID)
const user = await User.findByIdAndUpdate(
{ _id: userId },
{ $pull: { "skills": { skill: skillId }}},
{ new: true}
);
I believe u are receiving ObjectID from your params. If so,Something like this should help.

Populate subdocument in parent document Array Mongoose

I'm very new to JavaScript and Mongoose. I'm building a small project using express, mongoose and node.js.
I have a mongoose model - Client that has an Array of Transactions
var Client = mongoose.model('Client', {
name: {
type: String,
required: true,
minlength: 1
},
email: {
type: String
},
phone: {
type: Number
},
createdAt: {
type: Number,
default: null
},
transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
_creator: {
type: mongoose.Schema.Types.ObjectId,
required: true
}
});
module.exports = {Client};
This is Transaction model:
var Client = require('./client');
var Transaction = mongoose.model('Transaction',{
_creator : { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
amount : {type: Number, min: 0},
date : {type: Number,default: null},
total: {type: Number,default: null}
});
module.exports = {Transaction};
When I POST a new Transaction it goes through and saves in db:
app.post('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
var transaction = new Transaction({
amount: req.body.amount,
date: new Date().getTime(),
total: req.body.total,
_creator: req.params.id
})
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
I am also able to GET all the transactions associated with the client:
app.get('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
Transaction.find({
_creator: id
}).then((transactions) => {
res.send({transactions});
}).catch((e) => {
res.status(400).send();
});
});
But when I make a GET call to '/clients' - Array of Transactions is empty:
{
"clients": [
{
"_id": "1095d6de3867001108b803",
"name": "Peter",
"email": "peter#gmail.com",
"phone": 1232321,
"_creator": "5321df6d57868ec7001108b801",
"__v": 0,
"transactions": [],
"createdAt": null
} ]
}
And this is the GET call to /clients
app.get('/clients', authenticate, (req, res) => {
Client.find({
_creator: req.user._id,
})
.populate('transactions.transaction')
.then((clients) => {
res.send({clients});
}, (e) => {
res.status(400).send(e);
console.log('Unable to get clients', e);
})
});
I know that I'm likely doing something completely wrong but I don't know where I need to look for my mistake. Please help!
I would check if the client exist before adding a transaction. A transaction needs a client first.
Forewarn, I'm not a fan of then and catch so this answer does not use it. I normally use async.js when dealing with multiple asynchronous operations.
Anyways, I would do it like
app.post('/clients/:id/transactions', authenticate, (req, res) => {
Client.findOne({ _id: req.params.id }, (err, client) => {
if (err)
return res.status(400).send(err);
if (!client)
return res.status(400).send(new Error('No client'));
Transaction.create({
amount: req.body.amount,
date: new Date(), // I don't think you need .getTime()
total: req.body.total,
_creator: client._id
}, (err, transaction) => {
if (err)
return res.status(400).send(err);
client.transactions.push(transaction._id);
client.save(err => {
if (err)
return res.status(400).send(err);
res.json(transaction);
});
});
});
});
Good idea to also turn on debugging mode to see your queries: mongoose.set('debug', true).
You might also find using timestamps option for Transaction schema more useful than explicitly using date field
To get clients with their transactions
app.get('/clients', authenticate, (req, res) => {
Client.find({ _creator: req.user._id }).populate('transactions').exec((err, clients) => {
if (err)
return res.status(400).send(err);
res.json(clients);
});
});
so first of all i don't exactly know what _creator key in Client model representing, it's probably user identifier who has some clients, but if I'm wrong please correct me.
Honestly I don't know why you are making two way document connection, (keeping client in transactions, and also keeping transactions in clients) in my opinion first option is better for mongodb and using that you can easily get transaction's list with find, or mongodb aggregation, but you can't get data using populate.
In second option you need to remember that one document could have maximum 16MB. And also keeping thousands of transactions in one array is not well for performance. Think about example that you have 5000 transaction and you want to show list with pagination (50 records per page), with array option you have to get whole document, and splice array to 50 records. In first option you could use mongodb skip and limit. Please think about it.
Returning to question, mistake you are doing is here:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
Here you don't exactly say how this document should have to updated about.
Mongoose in method findOneAndUpdate using mongodb findAndModify method. But params are used from update.
https://docs.mongodb.com/manual/reference/method/db.collection.update/
And also documentation says that you what params like:
Query#findOneAndUpdate([query], [doc], [options], [options.passRawResult], [options.strict], [callback])
So first query param is mongo query to find one document in database, next param is object with updating query, and after that you could send some additional options in third param. So your code should looks like this:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
}, {
$addToSet: {
transactions: doc._id,
}
});
res.send(doc);
You could use addToSet or push both are putting element into array, but addToSet avoiding duplicates in array. And as you se we push new transaction identifier into this array. And after all you only populate transaction key.
I hope I helped. If you have any questions please ask.

Categories