Reputation: 48899
When sending a request to /customers/41224d776a326fb40f000001
and a document with _id
41224d776a326fb40f000001
does not exist, doc
is null
and I'm returning a 404
:
Controller.prototype.show = function(id, res) {
this.model.findById(id, function(err, doc) {
if (err) {
throw err;
}
if (!doc) {
res.send(404);
}
return res.send(doc);
});
};
However, when _id
does not match what Mongoose expects as "format" (I suppose) for example with GET /customers/foo
a strange error is returned:
CastError: Cast to ObjectId failed for value "foo" at path "_id".
So what's this error?
Upvotes: 213
Views: 430955
Reputation: 577
const { id } = req.params;
if (!id.match(/^[0-9a-fA-F]{24}$/)) {
return res.status(400).json({message: "ID is not a valid MongoDB _id, Please Check ID"})
}
You will get the response back if id is not a really 24char long :
{message: "ID is not a valid MongoDB _id, Please Check ID"}
Upvotes: 1
Reputation: 311835
Mongoose's findById
method casts the id
parameter to the type of the model's _id
field so that it can properly query for the matching doc. This is an ObjectId
but "foo"
is not a valid ObjectId
so the cast fails.
This doesn't happen with 41224d776a326fb40f000001
because that string is a valid ObjectId
.
One way to resolve this is to add a check prior to your findById
call to see if id
is a valid ObjectId
or not like so:
if (id.match(/^[0-9a-fA-F]{24}$/)) {
// Yes, it's a valid ObjectId, proceed with `findById` call.
}
Upvotes: 271
Reputation: 697
The way I fix this problem is by transforming the id into a string
I like it fancy with the backtick:
`${id}`
this should fix the problem with no overhead
UPDATE OCT 2022
it would be best if you now used the :
{id: id} // if you have an id property defined
or
{_id: new ObjectId(id)} // and search for the default mongodb _id
Upvotes: 3
Reputation: 324
if are using findByIdAndDelete method,then verify {_id:id}
this object.
i.e this.model.findByIdAndDelete({_id:id}).exec()
Upvotes: 0
Reputation: 11
You are having the castError because the next route you called after the id route could not be attached to the id route. You have to declare the id route as one last route.
Upvotes: 1
Reputation: 88
In my case, similar routes caused this problem.
Router.get("/:id", getUserById);
Router.get("/myBookings",getMyBookings);
In above code, whenever a get request to route "/myBookings" is made, it goes to the first route where req.params.id is equals to "myBookings" which is not a valid ObjectId.
It can be corrected by making path of both routes different.
Something like this
Router.get("/user/:id", getUserById);
Router.get("/myBookings",getMyBookings);
Upvotes: 2
Reputation: 597
could happen if you are sending less or more then 24 characters string as id
Upvotes: 0
Reputation: 13
In my case the parameter id length was 25, So I trimmed first character of parameter id and tried. It worked.
Blockquote
const paramId = req.params.id;
if(paramId.length === 25){
const _id = paramId.substring(1, 25);
}
To change the string object to ObjectId instance fromString() method is not exist anymore. There is a new method createFromHexString().
const _id = mongoose.Types.ObjectId.fromString(id); // old method not available
const _id = mongoose.Types.ObjectId.createFromHexString(id); // new method.
Upvotes: 0
Reputation: 417
If above solutions do not work for you.
Check if you are sending a GET request to a POST route.
It was that simple and stupid for me.
Upvotes: 4
Reputation: 293
This might be a case of routes mismatch if you have two different routes like this
router.route("/order/me") //should come before the route which has been passed with params
router.route("/order/:id")
then you have to be careful putting the route that is using a param after the regular route that worked for me
Upvotes: 28
Reputation: 1
I had the same error, but in a different situation than in the question, but maybe it will be useful to someone.
The problem was adding buckles:
Wrong:
const gamesArray = [myId];
const player = await Player.findByIdAndUpdate(req.player._id, {
gamesId: [gamesArray]
}, { new: true }
Correct:
const gamesArray = [myId];
const player = await Player.findByIdAndUpdate(req.player._id, {
gamesId: gamesArray
}, { new: true }
Upvotes: 0
Reputation: 7043
it happens when you pass an invalid id to mongoose. so first check it before proceeding, using mongoose isValid
function
import mongoose from "mongoose";
// add this inside your route
if( !mongoose.Types.ObjectId.isValid(id) ) return false;
Upvotes: 12
Reputation: 314
ObjectId is composed of following things.
Correct way to validate if the objectId is valid is by using static method from ObjectId class itself.
mongoose.Types.ObjectId.isValid(sample_object_id)
Upvotes: 1
Reputation: 11327
You could either validate every ID before using it in your queries (which I think is the best practice),
// Assuming you are using Express, this can return 404 automatically.
app.post('/resource/:id([0-9a-f]{24})', function(req, res){
const id = req.params.id;
// ...
});
... or you could monkey patch Mongoose to ignore those casting errors and instead use a string representation to carry on the query. Your query will of course not find anything, but that is probably what you want to have happened anyway.
import { SchemaType } from 'mongoose';
let patched = false;
export const queryObjectIdCastErrorHandler = {
install,
};
/**
* Monkey patches `mongoose.SchemaType.prototype.castForQueryWrapper` to catch
* ObjectId cast errors and return string instead so that the query can continue
* the execution. Since failed casts will now use a string instead of ObjectId
* your queries will not find what they are looking for and may actually find
* something else if you happen to have a document with this id using string
* representation. I think this is more or less how MySQL would behave if you
* queried a document by id and sent a string instead of a number for example.
*/
function install() {
if (patched) {
return;
}
patch();
patched = true;
}
function patch() {
// @ts-ignore using private api.
const original = SchemaType.prototype.castForQueryWrapper;
// @ts-ignore using private api.
SchemaType.prototype.castForQueryWrapper = function () {
try {
return original.apply(this, arguments);
} catch (e) {
if ((e.message as string).startsWith('Cast to ObjectId failed')) {
return arguments[0].val;
}
throw e;
}
};
}
Upvotes: 3
Reputation: 133
I was having problems with this and fixed doing mongoose.ObjectId(id)
without Types
Upvotes: 1
Reputation: 1319
As of Nov 19, 2019
You can use isValidObjectId(id)
from mongoose version 5.7.12
https://mongoosejs.com/docs/api/mongoose.html#mongoose_Mongoose-isValidObjectId
Upvotes: 7
Reputation: 21
I was faced with something similar recently and solved it by catching the error to find out if it's a Mongoose ObjectId error.
app.get("/:userId", (req, res, next) => {
try {
// query and other code here
} catch (err) {
if (err.kind === "ObjectId") {
return res.status(404).json({
errors: [
{
msg: "User not found",
status: "404",
},
],
});
}
next(err);
}
});
Upvotes: 2
Reputation: 4044
In my case, I had to add _id: Object
into my Schema, and then everything worked fine.
Upvotes: 9
Reputation: 788
Detecting and Correcting the ObjectID Error
I stumbled into this problem when trying to delete an item using mongoose and got the same error. After looking over the return string, I found there were some extra spaces inside the returned string which caused the error for me. So, I applied a few of the answers provided here to detect the erroneous id then remove the extra spaces from the string. Here is the code that worked for me to finally resolve the issue.
const mongoose = require("mongoose");
mongoose.set('useFindAndModify', false); //was set due to DeprecationWarning: Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the `useFindAndModify`
app.post("/delete", function(req, res){
let checkedItem = req.body.deleteItem;
if (!mongoose.Types.ObjectId.isValid(checkedItem)) {
checkedItem = checkedItem.replace(/\s/g, '');
}
Item.findByIdAndRemove(checkedItem, function(err) {
if (!err) {
console.log("Successfully Deleted " + checkedItem);
res.redirect("/");
}
});
});
This worked for me and I assume if other items start to appear in the return string they can be removed in a similar way.
I hope this helps.
Upvotes: 0
Reputation: 64
//Use following to check if the id is a valid ObjectId?
var valid = mongoose.Types.ObjectId.isValid(req.params.id);
if(valid)
{
//process your code here
} else {
//the id is not a valid ObjectId
}
Upvotes: 2
Reputation: 3775
I had to move my routes on top of other routes that are catching the route parameters:
// require express and express router
const express = require("express");
const router = express.Router();
// move this `/post/like` route on top
router.put("/post/like", requireSignin, like);
// keep the route with route parameter `/:postId` below regular routes
router.get("/post/:postId", singlePost);
Upvotes: 44
Reputation: 28847
Cast string to ObjectId
import mongoose from "mongoose"; // ES6 or above
const mongoose = require('mongoose'); // ES5 or below
let userid = _id
console.log(mongoose.Types.ObjectId(userid)) //5c516fae4e6a1c1cfce18d77
Upvotes: 0
Reputation: 428
I have the same issue I add
_id: String .in schema then it start work
Upvotes: 26
Reputation: 373
Always use mongoose.Types.ObjectId('your id')
for conditions in your query it will validate the id field before running your query as a result your app will not crash.
Upvotes: 1
Reputation: 726
if(mongoose.Types.ObjectId.isValid(userId.id)) {
User.findById(userId.id,function (err, doc) {
if(err) {
reject(err);
} else if(doc) {
resolve({success:true,data:doc});
} else {
reject({success:false,data:"no data exist for this id"})
}
});
} else {
reject({success:"false",data:"Please provide correct id"});
}
best is to check validity
Upvotes: 6
Reputation: 8351
This is an old question but you can also use express-validator package to check request params
express-validator version 4 (latest):
validator = require('express-validator/check');
app.get('/show/:id', [
validator.param('id').isMongoId().trim()
], function(req, res) {
// validation result
var errors = validator.validationResult(req);
// check if there are errors
if ( !errors.isEmpty() ) {
return res.send('404');
}
// else
model.findById(req.params.id, function(err, doc) {
return res.send(doc);
});
});
express-validator version 3:
var expressValidator = require('express-validator');
app.use(expressValidator(middlewareOptions));
app.get('/show/:id', function(req, res, next) {
req.checkParams('id').isMongoId();
// validation result
req.getValidationResult().then(function(result) {
// check if there are errors
if ( !result.isEmpty() ) {
return res.send('404');
}
// else
model.findById(req.params.id, function(err, doc) {
return res.send(doc);
});
});
});
Upvotes: 1
Reputation: 405
OR you can do this
var ObjectId = require('mongoose').Types.ObjectId;
var objId = new ObjectId( (param.length < 12) ? "123456789012" : param );
as mentioned here Mongoose's find method with $or condition does not work properly
Upvotes: 0
Reputation: 5849
You can also use ObjectId.isValid like the following :
if (!ObjectId.isValid(userId)) return Error({ status: 422 })
Upvotes: 3