Duplicate key error handling and unhandled promise rejection with mongoose - node.js

I have a user schema which follows the given schema.
According to the purpose I let the user signup for the site and later allow them to update their cars and their car numbers.
The car plate number must be a unique string while the name could be anything.
Here is my Schema ->
const mongoose = require('mongoose');
require('mongoose-type-email')
const Email = mongoose.Types.Email;
const Schema = mongoose.Schema;
var carNum = new Schema ({
plateNum : {
type : String,
unique : true,
sparse: true
},
name : {
type : String
},
price : {
type : Number
}
});
var userSchema = new Schema({
name : {
type : String,
required : true
},
email : {
type : Email,
required : true,
unique : true
},
password : {
type : String,
required : true
},
car : [carNum]
});
var users = mongoose.model('user',userSchema);
module.exports = users;
Here is the code that handles the insertion.
.post((req,res,next) =>{
var plate = req.body.plateNum;
var carname = req.body.carName;
users.findOne({'_id': `${userId}`})
.then((result) =>{
res.statusCode = 200;
res.setHeader('Content-type',"application/json");
res.json(result)
result.car.push({
plateNum : plate,
name : carname,
})
result.save()
})
.catch((err) => {
console.log( "Error : "+ err);
res.send('The name plate number already exists')
});
})
When i try to send a duplicate or already existing name plate it returns unhandled promise rejection warning with mongoDB error.
UnhandledPromiseRejectionWarning: MongoError: E11000 duplicate key error collection: tarp.users index: car.plateNum_1 dup key: { car.plateNum: "asd" }
at Function.create (C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\core\error.js:44:12)
at toError (C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\utils.js:150:22)
at coll.s.topology.update (C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\operations\common_functions.js:373:39)
at handler (C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\core\sdam\topology.js:1000:24)
at wireProtocol.(anonymous function) (C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\core\sdam\server.js:457:5)
at C:\Users\Harsh Verma\Documents\VIT\TARP\Project\Tarp Project\node_modules\mongodb\lib\core\connection\pool.js:408:18
at process._tickCallback (internal/process/next_tick.js:61:11)
(node:14348) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().
I do have .catch placed there so is there something missing there.
I want to let the user know that the plate already exists.
However I did manage to get this working but I do not know why this is working not the first thing.
Here is the working thing
.post((req,res,next) =>{
var plate = req.body.plateNum;
var carname = req.body.carName;
users.findOne({'_id': `${userId}`})
.then((result) =>{
res.statusCode = 200;
res.setHeader('Content-type',"application/json");
// res.send(result);
result.car.push({
plateNum : plate,
name : carname,
})
result.save()
.then((saved) => {
// Nothing gets consoled
console.log(saved)
})
.catch((err) => {
console.log( "Error : "+ err);
res.send('The name already exists')
});
})
.catch((err) => res.send('Unable to save : ' + err));
})

First one is not working because the Duplicate exception is thrown by .save() not by .findOne() and once you add catch block to the save() it works.

Related

Can't retrieve embedded object from a MongoDB document - MERN stack

I am using the MERN stack for my current project. So I am facing this problem:
Let's say that I have the following document in MongoDB :
{
name: "Tesla Inc.",
category: "Automotive",
contact: {
state : {
name: "Texas",
city: "Austin",
address: {
streetName: "Tesla Road",
number: '1'
}
}
}
}
What I get as response after using findOne({ name : "Tesla"}) is :
{_id: '637e4397f6723844191aa03d', name: 'Tesla', category:
'Automotive', contact: {…}}
As you can see contact object is undefined
Follows my coding process
This is my Express route for quering the database :
storeRoutes.route("/enterprise").get(function (req, res) {
let db_connect = dbo.getDb("res");
const query = { name : "Tesla"};
db_connect
.collection("stores")
.findOne(query,function (err, result) {
if (err) throw err;
res.json(result);
});
});
Result: After typing browser url http://localhost:5000/enterprise returns the expected value:
{"_id":"637e4397f6723844191aa03d","name":"Tesla","category":"Automotive","contact":{"state":{"name":"Texas","city":"Austin","address":{"streetName":"Tesla Road","number":"1"}}}}
This a Retriever Data Function that returns the data object:
function GetEnterprise() {
const [store, setStore] = useState({
})
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:5000/enterprise`);
if (!response.ok) {
const message = `An error has occurred: ${response.statusText}`;
window.alert(message);
return;
}
const record = await response.json();
if (!record) {
// window.alert(`Record with id ${id} not found`);
window.alert(`Record with id not found`);
return;
}
setStore(record);
}
fetchData();
return;
}, [1]);
//debugging
console.log('tesla: ' + store);
window.store = store;
let res_json = JSON.stringify(store);
console.log('res_json :' + res_json);
return store;
}
Result:
Before GetEnterprise() function returns store I have added these 4 lines of code for debugging:
console.log('tesla: ' + store);
window.store = store;
let res_json = JSON.stringify(store);
console.log('res_json :' + res_json);
1st line logs [object Object] which is not that informative for what I am getting back as a response.
So I came up with 2nd line which enables to debug directly from the browser console.
After I type store my console logs:
{_id: '637e4397f6723844191aa03d', name: 'Tesla', category: 'Automotive', contact: {…}}
So my contact object is missing(undefined).
Now fun fact is the 3rd and 4rd lines :
let res_json = JSON.stringify(store);
console.log('res_json :' + res_json);
My console logs the whole object as expected:
{"_id":"637e4397f6723844191aa03d","name":"Tesla","category":"Automotive","contact":{"state":{"name":"Texas","city":"Austin","address":{"streetName":"Tesla Road","number":"1"}}}}
Which is really weird.
I guess it has something to do with the async and await functions. But I am not sure.
What am I doing wrong?
Any suggestions ..?

How do I solve "CastError: Cast to ObjectId failed for value "undefined" (type string) at path "_id" for model "Task""? [duplicate]

This question already has answers here:
Mongoose: CastError: Cast to ObjectId failed for value "[object Object]" at path "_id"
(17 answers)
Closed 3 months ago.
I am still new to Node JS. I am trying to make a book directory using Node JS and Mongo DB. Every time I press the delete button to delete a book it shows me this error
CastError: Cast to ObjectId failed for value "undefined" (type string) at path "_id" for model
"Task"
BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters
This my server.js:
app.delete("/api/books/:id", async (req, res) => {
try {
const { id: id } = req.params;
console.log(id);
const task = await Task.findOneAndDelete({ _id: id });
if (!task) {
return res.status(404).json({ msg: `No task with id :${id}` });
}
res.status(200).json(task);
} catch (error) {
console.log(error);
}
});
so i was getting this error -> CastError: Cast to ObjectId failed for value ...
I log the id out and checked if it corresponded to my id in my Database
since i confirmed it was there and still getting the same error, I then used .trim() method to remove any blank space from the _id and the error was solved
check my code below 👇 and see if it helps
const checkItemId = req.body.checkbox;
console.log(checkItemId);
// find the item by id and remove it make sure youuse .trim to remove any blank space
Item.findByIdAndRemove(checkItemId.trim(), (err) => {
if(err){
console.log(err);
}else{
console.log("Removed Succesfull");
}
})
I also had this problem once.
I got this error because the ObjectId passed in through the params didn't existed in my database. Hence, this threw an exception.
Workaround for this:
//add this line as a check to validate if object id exists in the database or not
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).json({ msg: `No task with id :${id}` });
Updated code:
app.delete("/api/books/:id", async (req, res) => {
try {
const { id: id } = req.params;
console.log(id);
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).json({ msg: `No task with id :${id}`
});
const task = await Task.findOneAndDelete({ _id: id });
res.status(200).json(task);
} catch (error) {
console.log(error);
}
});
Check if below code working!
if (!mongoose.Types.ObjectId.isValid(id)) {
throw new HttpException('Not a valid ID!', HttpStatus.NOT_FOUND);
} else {
return id;
}
I solved the issue by removing blank space in my id ,use const id = req.params.id
then const trimmed_id = id.trim(), pass trimmed_id on your findById
it will work

Blank document and unhandled promise rejection when making an add subdocument request

I am working on my first request that would add a subdocument to a document in MongoDB and I'm struggling with it. My database is a collection of users, and each user has an array of words they working on learning to translate in the application I am building. I am currently having two issues
when I make a request in postman to add a new word to my user's array of words, I add a new object that has ID, but none of the other property value pairs that I have in the word sub-model, and in the request(greek, english, success, timestamp).
my command prompt gives me the following errors
(node:8320) UnhandledPromiseRejectionWarning: Unhandled promise
rejection. This error originated either by throwing inside of an async
function without a catch block, or by rejecting a promise which was
not handled with .catch(). (rejection id: 3) (node:8320)
UnhandledPromiseRejectionWarning: ValidationError: user validation
failed: words.0.greek: Path greek is required., words.0.english:
Path english is required., words.0.success: Path success is
required., words.0.timeStamp: Path timeStamp is required.
The second error is confusing because in my mind it should be word.greek and words.english to get the value from the each word object. However, it adds a 0 between the object and its property/value pair.
My mongoose model for the subdocument is as follows
const wordSchema = new Schema({
greek: {
type: String,
required: true,
trim: true,
minlength: 1,
index: { unique: true, sparse: true },
},
english: {
type: String,
required: true,
minlength: 1
},
success: {
type: Number,
required: true
},
timeStamp: {
type: Date,
required: true
},
});
This is my request to add the word to the User's array of words.
router.post("/update/:id",(req, res) =>{
console.log(req.body.greek)
var greek = req.body.greek;
var english = req.body.english;
var success = req.body.success;
var timeStamp = req.body.timeStamp
var newWord = {
greek: greek,
english: english,
success: success,
timeStamp: timeStamp
}
User.findById(req.params.id)
.then((user) => {
user.words.push(newWord);
user.save()
res.status(200).json(user)
.catch((err) => {res.status(400).json(err)})
})
.catch((err) => {res.status(400).json("Error: "+err)})
});
Any help would be greatly appreciated! I've done some googling on adding subdocuments to a document, but I still haven't found the solution.
Instead of fetching and then updating the document you can directly update the document in one DB call.
router.post("/update/:id",(req, res) =>{
console.log(req.body.greek)
var greek = req.body.greek;
var english = req.body.english;
var success = req.body.success;
var timeStamp = req.body.timeStamp
var newWord = {
greek: greek,
english: english,
success: success,
timeStamp: timeStamp
}
User.findOneAndUpdate({ _id: req.params.id }, {
$push: {
words: newWord
}
})
.then((user) => {
if(!user){
return res.status(404).json({
message: 'Not User matches given ID'
});
}
res.status(200).json(user);
})
.catch((err) => {res.status(400).json("Error: "+err)})
});
one thing I see is user.save() will return a promise, which you do not handle, hence the document will not be save. maybe consider:
User.findById(req.params.id)
.then(async (user) => {
user.words.push(newWord);
await user.save()
res.status(200).json(user)
.catch((err) => {res.status(400).json(err)})
})
.catch((err) => {res.status(400).json("Error: "+err)})

What's the best Async/Await approach using Mongoose + Node.js?

I'd like to know if this kind of async/await approach with mongoose is correct. I still need to use .exec and then returning the promise with mongoose or I can leave things like this. Here my code snippet:
This is the user controller for example:
/* Func to update one user by id */
const updateUser = async (id, user) => {
const filter = {_id: id};
const update = {name: user.name, email: user.email};
const result = await User.findOneAndUpdate(filter, update, {new: true});
return result;
};
This is the route:
/* PATCH update user passing the id in params */
router.patch('/list/:id/update', async (req, res, next) => {
try {
const data = await usersController.updateUser(req.params.id, {
name: req.body.name,
email: req.body.email,
});
res.status(data ? 200 : 404).json({
result: data,
message: 'User updated',
});
} catch (e) {
res.status(500).json({
result: e.toString(),
});
}
});
Is this approach correct using mongoose or I need to use the async calling .exec().then().catch() after the query?
According to mongoose documentation, as far as functionality is concerned, these two are equivalent. However, they recommend using the exec because that gives you better stack traces:
const doc = await Band.findOne({ name: "Guns N' Roses" }); // works
const badId = 'this is not a valid id';
try {
await Band.findOne({ _id: badId });
} catch (err) {
// Without `exec()`, the stack trace does **not** include the
// calling code. Below is the stack trace:
//
// CastError: Cast to ObjectId failed for value "this is not a valid id" at path "_id" for model "band-promises"
// at new CastError (/app/node_modules/mongoose/lib/error/cast.js:29:11)
// at model.Query.exec (/app/node_modules/mongoose/lib/query.js:4331:21)
// at model.Query.Query.then (/app/node_modules/mongoose/lib/query.js:4423:15)
// at process._tickCallback (internal/process/next_tick.js:68:7)
err.stack;
}
try {
await Band.findOne({ _id: badId }).exec();
} catch (err) {
// With `exec()`, the stack trace includes where in your code you
// called `exec()`. Below is the stack trace:
//
// CastError: Cast to ObjectId failed for value "this is not a valid id" at path "_id" for model "band-promises"
// at new CastError (/app/node_modules/mongoose/lib/error/cast.js:29:11)
// at model.Query.exec (/app/node_modules/mongoose/lib/query.js:4331:21)
// at Context.<anonymous> (/app/test/index.test.js:138:42)
// at process._tickCallback (internal/process/next_tick.js:68:7)
err.stack;
}

Mongoose saves invalid data without throwing validation errors if model.validate() is called first

MongoDB 4.2.2 and Mongoose 5.8.3 (latest) and NodeJS 13.3.0 (Windows x64)
If I create a schema and model, then create an instance of the model and add some data, then run validate(), then save(): even if validate() fails, the data is saved into the collection, without throwing an additional validation error.
Is this a bug, or am I doing something wrong?
Here's the test code:
var mongoose = require('mongoose')
mongoose.connect("mongodb://user:pass#localhost/mydb")
db = mongoose.connection
var Schema = mongoose.Schema
var PartSchema = new Schema({
name: {
type: String,
required: true,
validate: {
validator: (v) => v !== 'asdf' // Don't allow name to be 'asdf'
}
},
number: {
type: String,
required: true,
validate: {
validator: (v) => !v.includes(' ') // Don't allow spaces in part number.
}
}
})
var ProductSchema = new Schema({
name: String,
parts: [PartSchema]
})
var Part = mongoose.model('Part', PartSchema)
var Product = mongoose.model('Product', ProductSchema)
var p1 = new Product({name:"Baseball Bat", parts:[ new Part({name:"First part", number: "003344"}), new Part({name: "Second part", number: "554422"}) ]})
p1.parts.push(new Part({name: "No number, so invalid"})) // this one is invalid because no part number is specified (required)
p1.parts.push(new Part({name: 'asdf', number: 'zzzzzaaaa'}))
p1.parts.push(new Part({name: 'bbbb', number: 'with a space'})) // This one is invalid because number has spaces.
p1.validate()
.then(() => {console.log('Validation successful')})
.catch((err) => { console.log("Validation failed.")})
p1.save()
.then(()=>{ console.log("Saved successfully")})
.catch((err)=>{console.log("Save ERROR", err)})
Running this code yields the following:
Validation failed.
Saved successfully
And the new document appears in the database:
However, if I remove the p1.validate() before calling save(), the save function's catch() block triggers and the item is not saved:
Save ERROR Error [ValidationError]: Product validation failed: parts.2.number: Path `number` is required., parts.3.name: Validator failed for path `name` with value `asdf`, parts.4.number: Validator failed for path `number` with value `with a space`
at ValidationError.inspect
... snipped
May be you need to use p1.save() inside the promise chain.
p1.validate()
.then(res => {
console.log("Validation successful");
})
.then(() => {
return p1.save();
})
.then(res => {
console.log("saved success ", res);
})
.catch(err => {
console.log("Some error.", err);
});

Resources