What's the best Async/Await approach using Mongoose + Node.js? - 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;
}

Related

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

Mongoose: the isAsync option for custom validators is deprecated

The Stripe Rocket Rides demo uses isAsync in a validator:
// Make sure the email has not been used.
PilotSchema.path('email').validate({
isAsync: true,
validator: function(email, callback) {
const Pilot = mongoose.model('Pilot');
// Check only when it is a new pilot or when the email has been modified.
if (this.isNew || this.isModified('email')) {
Pilot.find({ email: email }).exec(function(err, pilots) {
callback(!err && pilots.length === 0);
});
} else {
callback(true);
}
},
message: 'This email already exists. Please try to log in instead.',
});
This method throws an error with a reference
DeprecationWarning: Mongoose: the `isAsync` option for custom validators is deprecated. Make your async validators return a promise instead: https://mongoosejs.com/docs/validation.html#async-custom-validators
The MongoDB page quoted has this code:
const userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise.
validate: () => Promise.reject(new Error('Oops!'))
},
email: {
type: String,
// There are two ways for an promise-based async validator to fail:
// 1) If the promise rejects, Mongoose assumes the validator failed with the given error.
// 2) If the promise resolves to `false`, Mongoose assumes the validator failed and creates an error with the given `message`.
validate: {
validator: () => Promise.resolve(false),
message: 'Email validation failed'
}
}
});
I am new to NodeJS and I don't see how to adapt the MongoDB code to the Rocket Rides demo. Neither Implicit async custom validators (custom validators that take 2 arguments) are deprecated in mongoose >= 4.9.0 nor Mongoose custom validation for password helped.
How can I verify the uniqueness of email addresses and avoid that error?
try this, I had the same problem.
To fix it use a separate async function that you call with await.
// Make sure the email has not been used.
PilotSchema.path('email').validate({
validator: async function(v) {
return await checkMailDup(v, this);
},
message: 'This email already exists. Please try to log in instead.',
});
async function checkMailDup(v, t) {
const Pilot = mongoose.model('Pilot');
// Check only when it is a new pilot or when the email has been modified.
if (t.isNew || t.isModified('email')) {
try {
const pilots = await Pilot.find({ email: v });
return pilots.length === 0;
} catch (err) {
return false;
}
} else {
return true;
}
}
Let me know if it works.
I used the following references:
Mongoose async custom validation not working as expected
Cheers!

Duplicate key error handling and unhandled promise rejection with mongoose

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.

I define nested schema , when i fill the inputs it returns nothing and not save in Mongodb

I define nested schema but when I send input data returns nothing,
how can I solve this issue ?
this is my result:
{
"message": "handeling post request to /user-api",
"CreatedUserInfo": {
"_id": "5cbb7fbaad28fe209099a57c"
}
}
this is my code :
const userEduSchema = new mongoose.Schema(
{
eduLevel : String ,
eduField : String,
eduInst :String,
eduCity :String,
eduDate :Date,
proposalTitle :String
}
)
const allEduSchema = new mongoose.Schema(
{
bsc: userEduSchema,
master: userEduSchema,
phd: userEduSchema ,
}
)
module.exports = mongoose.model('Users', allEduSchema )
and it is my user.js for save inpute data in MongoDB I don't know this is true or not:
const userModels = require('../../models/userModels')
router.post('/', (req , res, next) => {
const user = new userModels({
_id : new mongoose.Types.ObjectId,
eduLevel :req.body.eduLevel,
eduField :req.body.eduField,
eduInst :req.body.eduInst,
eduCity :req.body.eduCity,
eduDate :req.body.eduDates,
proposalTitle :req.body.proposalTitle,
})
user.save().then(result =>{
console.log(result)
}).catch (err => {
console.log(err)
})
res.status(201).json ({
message:'handeling post request to /user-api',
CreatedUserInfo : user
})
})```
The problem was in defining the way of get the inputs,I must define the nested object and inside of that put the request bodies as well as I define the nested schema.
bsc:{
bscEduLevel :req.body.bscEduLevel,
bscEduField :req.body.bscEduField,
bscEduInst :req.body.bscEduInst,
bscEduCity :req.body.bscEduCity,
bscEduDate :req.body.bscEduDate,
bscProposalTitle :req.body.bscProposalTitle
}
Have you defined userSchema? If not, you are trying to export a schema which does not exist. Again, in case you have not defined userSchema and you want to export allEduSchema, replace the last line of your model file with this:
const EduSchema = mongoose.model("EduSchema", allEduSchema);
module.exports = EduSchema;
Before you create a new user ,you need to require your "User" moongose Schema. Just like dimitris tseggenes said.
I use async function and try/catch to handle this problem.Maybe you could....
router.post("/", async (request, response) => {
try {
const user = new Users({new user's data here..... });
const result = await user.save();
response.send(result);
} catch (error) {
response.status(400).send(error);
}
});
try catch could avoid some unexpected error and handle it.

Bookshelf.js - Passing models to collection.atttach()

The bookshelf documentation indicates that I should be able to pass an array of models into collection.attach(), and they demonstrate this with the following code:
var admin1 = new Admin({username: 'user1', password: 'test'});
var admin2 = new Admin({username: 'user2', password: 'test'});
Promise.all([admin1.save(), admin2.save()])
.then(function() {
return Promise.all([
new Site({id: 1}).admins().attach([admin1, admin2]),
new Site({id: 2}).admins().attach(admin2)
]);
})
It doesn't seem to work in my case, however. My save operation works fine if I pass in an array of ids:
export function create({ blocks, tags, ...rest }) {
const attributes = {
blocks: JSON.stringify(blocks),
...rest
}
return Post.forge(attributes).save().then(post => {
return post.tags().attach(tags.map(t => t.id)).then(() => post.refresh())
})
}
However, if I try to pass in the tags instead, like this:
return post.tags().attach(tags).then(() => post.refresh())
Then I receive an error:
Unhandled rejection error: column "id" of relation "posts_tags" does not exist
Am I misreading the documentation? Should I not be able to do this?

Resources