How to properly handle error on mongoose CRUD - node.js

I am building a very simple RESTFUL app to see how Node and Mongo work together.
A made a function to find a "Persona" document on a mongodb by Id, so here is the code:
function getPersonabyId(req,res)
{
//en params del objeto request esta lo que llega por get
let idPersona=req.params.id;
//mongoose trae un método para pillar por id
Persona.findById(idPersona).exec((err,objPersona)=>
{
if(err)
{
res.status(500).send(err);
}
else{
res.status(200).send(objPersona);
}
})
}
And I use this route, where function is executed:
miniApp.get("/getPersonaById/:id",controladorPersona.getPersonabyId);
So, when I pass as parameter a valid ID, everything is ok, I get the proper Persona object in the response.
But when I use a not valid ID (no document with that ID exists on the mongodb), the error specified on the exec callback is thrown... but, isn´t supossed that this error should be thrown only in case there are server problems? a non existent ID should not be a 500 error, isn´t it?
I looked for info and I found this:
https://coursework.vschool.io/mongoose-crud/
Person.find((err, people) => {
// Note that this error doesn't mean nothing was found,
// it means the database had an error while searching, hence the 500 status
if (err) return res.status(500).send(err)
// send the list of all people
return res.status(200).send(people);
});
And reading the commented lines on the code above, confuses me even more... the error, as said in those comments, should be an error database or something similar, not a "not found" error... but the error is actually thrown when an object with that ID is not found!
This is the error I get with a non valid ID:
{
"message": "Cast to ObjectId failed for value \"5b105ba453401c41d0e3da2\" at path \"_id\" for model \"Persona\"",
"name": "CastError",
"stringValue": "\"5b105ba453401c41d0e3da2\"",
"kind": "ObjectId",
"value": "5b105ba453401c41d0e3da2",
"path": "_id"
}

As you are using mongoose, you can validate that the incoming value is an objectId or not by using mongoose.Types.ObjectId.isValid(req.params.id). This will return a boolean and possibly save you some effort or writing the regex for object Id validation.

You have defined your _id as ObjectId in schema. Not every string is a valid ObjectId. the id tha you are passing to findById is not valid. Object id should be 24 characters long string. Add this check before querying,
if (id.match(/^[0-9a-fA-F]{24}$/)) {
// Yes, it's a valid ObjectId, proceed with `findById` call.
} else{
//throw an error
}
or if you want to keep using any string as _id, define _id as String instead of ObjectId in your schema.

Related

How to avoid getting "Cast to ObjectId failed for value" after passing an invalid id to a query function in mongoose?

I have a route handler that take the id parameter from request object and uses it to find a document.
I thought that the findById function would return null if it doesn't find any document with the given id. So I created an if condition which generate an error with the appropriate message and status code. But it turns out the function automatically returns the following error if the id is invalid
But I do not want the "findById" query to generate error by itself. I want the tour variable to be null if the id is invalid so that my own implementation for handling 'Not found' exception works. How may I do so?
Try wrapping the line const tour = await Tour.findById(req.params.id) with a try catch statement like this:
try {
const tour = await Tour.findById(req.params.id);
} catch (error) {
next(new AppError(error.message, 404));
}
this won't solve the problem completely as you still need to convert the value of the id you're trying to query (I assume you're receiving a string) to a mongoose ObjectId. see this answer for this

Mongodb Cast error: Part of the url is sent to _id. How to get the id from the request

I have been searching on google and StackOverflow and this error has been around for quite a while but none of the proposed solutions was fit to my problem.
I have an endpoint
router.get('/posts/me', authController.isLoggedIn, catchErrors(blogController.myPosts))
to check which posts this user has
on the controller I have:
exports.myPosts = async (req, res) => {
const posts = await Post.findById({author: {$eq: req.user._id}})
res.json({'response':posts.length > 0 ? posts : 'You have no blog posts ☹️ ',
'status':200})
}
Mongo returns a weird error:
CastError: Cast to ObjectId failed for value "{ _id: 'me' }" at path "_id" for model "Post"
where 'me' is the last part of the URL. If I change, this part of the error changes as well.
I don't want to find posts by Id, I need to find all the posts where the author id is equal to the req.user_.id
The $where the operator is not valid on my atlas tier and I read that it isn't really efficient. The thing though is How can I overcome that error
and get a list of all the posts a user has created?
The complete error message:
CastError: Cast to ObjectId failed for value "{ _id: 'me' }" at path "_id" for model "Post"
at new CastError (F:\test\Projects\test\Backend\node_modules\mongoose\lib\error\cast.js:29:11)
at ObjectId.cast (F:\test\Projects\test\Backend\node_modules\mongoose\lib\schema\objectid.js:244:11)
at ObjectId.SchemaType.applySetters (F:\test\Projects\test\Backend\node_modules\mongoose\lib\schematype.js:948:12)
at ObjectId.SchemaType._castForQuery (F:\test\Projects\test\Backend\node_modules\mongoose\lib\schematype.js:1362:15)
at ObjectId.SchemaType.castForQuery (F:\test\Projects\test\Backend\node_modules\mongoose\lib\schematype.js:1352:15)
at ObjectId.SchemaType.castForQueryWrapper (F:\test\Projects\test\Backend\node_modules\mongoose\lib\schematype.js:1331:15)
at cast (F:\test\Projects\test\Backend\node_modules\mongoose\lib\cast.js:252:34)
at model.Query.Query.cast (F:\test\Projects\test\Backend\node_modules\mongoose\lib\query.js:4607:12)
at model.Query.Query._castConditions (F:\test\Projects\test\Backend\node_modules\mongoose\lib\query.js:1790:10)
at model.Query.<anonymous> (F:\test\Projects\test\Backend\node_modules\mongoose\lib\query.js:2045:8)
at model.Query._wrappedThunk [as _findOne] (F:\test\Projects\test\Backend\node_modules\mongoose\lib\helpers\query\wrapThunk.js:16:8)
at process.nextTick (F:\test\Projects\test\Backend\node_modules\kareem\index.js:369:33)
at process._tickCallback (internal/process/next_tick.js:61:11)
I am using passport.js to handle the auth
exports.isLoggedIn = (req, res, next) =>{
if(req.isAuthenticated()){
return next();
}
res.status(401).json({'online':false,
'message':'Oops you must be logged in to do that 😋',
'status':401})
}
To explain better what I am doing...
My index.js file has all my routes, I want to call a route using a get request that will (if the user is logged in) call a method that will query the db searching for all the posts that this user has published.
The url is fixed and I am not using it on the db query.
I get for the query the value of the user id that is on the req.
If I pass the id on the url and query the db using it, the error is gone but the db returns always null.
When I get all posts (using another route) I get this response:
"response": [
{
"text": [
"We still need to update this"
],
"tags": [],
"images": [],
"_id": "5d4c1c0266afb7629c2513b9",
"title": "Second Post",
"description": "This is the second post for our platform",
"author": "5d4923257b914233b834a7fb",
"created": "2019-08-08T12:56:34.720Z",
"slug": "second-post",
"id": "5d4c1c0266afb7629c2513b9"
}
The focus is on the author id:5d4923257b914233b834a7fb
If I change my endpoint to use the id from the url
router.get('/posts/:id', authController.isLoggedIn, catchErrors(blogController.myPosts))
const posts = await Post.find({author: {$eq: req.params.id}})
The response is null, the error is gone but mongo returns null.
The error is that you're passing a string, in this case me, instead of a MongoDB ObjectID.
I guess the reason why this route doesn't work while others do is that the others are something like this: /posts/:post_id. Your code assumes in this /posts/me route that me is the post ID and tries to find the post with ObjectID me which obviously will crash.
If you paste the contents of index.js I'll be able to pinpoint the problem.
As for not getting results when you're using the id from the request parameter, get rid of the eq operand and call the route with a user id
await Post.find({ author: req.params.id })
As weird as it might be, changing the kind of the request solved the problem.
router.post('/posts/me', authController.isLoggedIn, catchErrors(blogController.myPosts))
Even not sending any kind of data on the request, everything works as it is declared on my function.
Honestly, I don't understand why but as this solves my problem, I will stick with it.
The problem is also solved by changing the order of the router. If you have before this route, then something like this in the url '/posts/:id' and then perhaps '/posts/my' will no longer be the same as the match of the route with the one where there is an id.

CastError: Cast to string failed for value

I wanted to created a transaction module where after a successful transaction the users document(in this case user sends money to another user) will be updated as well.
a. in user.js(this is user model) besides name, pw, email (etc) I created this property which will hold the transaction related history of respective user. Please look at how I’ve used it:
transaction_history:[{
transactionid:String,
timestamp:String,
type:String,
balance:Number,
status:String
}]
b. when sender clicks send button in the form, a transaction document is created, & after this the user document(here the sender) should be updated along with transaction info.
//create transaction document, works fine
Transaction.create({transactionid:uniqid(),
timestamp:moment().format(datemask),
amount:balance,
sender:sendfrom,
receiver:sendto,
status:"done"
}, function(err, tr){
if(err) throw err;
else {
//I want sender document to update with transaction info
User.findOne({email:sendfrom}, function(err, sendfrom){
if(err) {console.log("error at sender side");}
else
if(sendfrom!=null){
// console.log("tr: "+tr); //fine
sendfrom.balance-=balance;
sendfrom.transaction_history.push({
transactionid:tr.transactionid,
// timestamp:tr.timestamp,
// type:"debit",
// balance:tr.amount,
// status:tr.status
}
);
sendfrom.save();
console.log("sender's current balance is: "+sendfrom.balance);
};
});
}});
c. But then I get this:
events.js:163
throw er; // Unhandled 'error' event
^
CastError: Cast to string failed for value "{ transactionid: '1amhrummxjhnhv0w4' }" at path "transaction_history"
Why this error occurs?I want your suggestion please! Thank you
You can't use the word 'type' as an object. Just rename to something else like 'something_type'.
Root of the problem is in the way I defined transaction_history property. This was supposed to be an array of objects syntactically[{}], but then I wrote its type to be String. So when I tried to insert a string as a value of ’type’, it throws error being unable to push string to ‘transaction_history’ object. To solve it, just need to remove type property. It’s my mistake to use a reserved word as a key in object.So I replaced 'type' by something else in model. that’s it!
At my case:
CastError: Cast to String failed for value "[ 'whateverValue_1', 'whateverValue_2' ]" at path "name"
Issue was that I have in form html name same word...
I.e:
<input name = "name"> Name
<input city = "name"> City
Looking for my mistake, I leave you other that could help for newbies as me:
words like type, model, never have to be in model Schema as main keywords!
There is a workaround provided by Mongoose:
You can use the field "type" in the object as long as you define its type, as such:
transaction_history: [
{
transactionId: String,
timestamp: String,
type: { type: String },
balance: Number,
status: String
}
]
source: https://www.typeerror.org/docs/mongoose/schematypes

Mongoose Error while performing delete

I am running into following error but I unable to completely grasp the understanding behind the error.
CastError: Cast to ObjectId failed for value "XYZ" at path "_id" for model "Partner"
I have my schema defined as following
var partnerList = new Schema (
{
partnerName: String,
supportedProducts: [String]
},
{
collection: 'partnerList'
}
);
module.exports = mongoose.model('Partner', partnerList);
The functionality of my delete function
delete: function (req, res) {
var removePartner = req.params.partnerName;
var promise = Partner.findByIdAndRemove(removePartner).exec();
promise.then(function removePartner(val) {
console.log('partner value removed');
res.send(val);
}).catch(function catchError(err){
console.error(err);
throw err;
});
}
I am trying to making a request to my node app service using
localhost:8443/data/XYZ, where i am passing the value 'XYZ' as the parameter. This value is used to delete the appropriate object from the database.
Basically the error means that whatever you pass as your "XYZ" url param is not a valid ObjectId.
Guessing from your code you use the "partner name" (probably some arbitrary string) instead of the database id of the partner. However findByIdAndRemove() requires you to specify an ObjectId as it uses this to identify which document to delete:
Model.findByIdAndRemove(id, [options], [callback])
Your delete API call could then look something like this: http://localhost:8443/data/59558ccd7acc4dd63ea88988. However for this the client needs to know the ObjectId of the partner.
So you have to either use the ObjectId of a partner in the URL, or use remove() to implement your custom delete query instead, for example like this (if name is the property you use to store your partner names):
Partner.remove({ name: partnerName }).exec();
Be careful however that this might remove multiple documents if your partner name is not unique, as remove will delete all documents matching the query.
In order to prevent this you can also use findOneAndRemove() using the same query. This would only remove one document at a time. If there are multiple partners with the same name it would remove the first one (depending on your sort order).

MongoDB/Mongoose returning empty array

I'm currently working on a project with mongodb/mongoose, and every time I purposely query for something that does not exist in the DB, I am getting a response with an empty array. This is my code for using Express to set up an API and return the data found in the DB:
app.get('/api/:id', function(req, res) {
var id = req.params.id;
Job.find({jobID: id}, function (err, foundJob) {
if (err) {
console.log(err);
}
else {
res.json(foundJob);
}
});
});
However, every time I go to localhost:3000/api/67 (I have no object with jobID: 67 in the database yet), the console does not print the error. It gives me a JSON response with an empty array. Any ideas to why this is happening? The weird part is that when I change jobID: id to _id: id, it does give me an error. Why doesn't it do that for the jobID field?
EDIT: Just to clarify, the reason why I do not want this behavior is because my program will never print the error, even if a job in my DB doesn't exist with that specified jobID.
It does something different than you think it does.
Job.find({jobID: id}, ... )
What this really says is give me array with all the documents in collection "Job" that have field "jobID" equal to some value.
What if there is no such document? Well, then the array will be empty. Without any error, of course, what should be an error here? You asked for all documents (given some filter) and an array with all such documents was returned. It is just a coincidence that the size of the array is zero, because there are no such documents.
If you want to check whether there is no such document then check whether the array is empty.
I don't know why it is giving you error when you change JobID to _id; what error exactly is it?
If you are interested only in one document, then there is method findOne that returns only the first document (or null if no such documents exist) instead of an array.
About error when you try to find something by it's __id: It gives you a error because __id is not String, it's ObjectId. So you should search for document with that __id like this: _id: ObjectId(id)
About empty string: If you want to display some kind of different message you should check if db returned something or is it rather empty string that got returned. Something like this:
app.get('/api/:id', function(req, res) {
var id = req.params.id;
Job.find({jobID: id}, function (err, foundJob) {
if(foundJob){
res.json(foundJob);
}else{
res.json("nothing found");
}
if (err) {
console.log(err);
}
});
});
Edit: I didnt realize that you had check for error, I changed code.
Returning an empty string is normal behavior for mongoose. You should handle your response like this:
if (err) {
//handle error
} else if (foundJob) {
//work with your data
} else {
//nothing was found
}
The error you get with _id must be irrelevant and is probably due to an invalid query.

Resources