200

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?

gremo
  • 47,186
  • 75
  • 257
  • 421

32 Answers32

260

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.
}
user16217248
  • 3,119
  • 19
  • 19
  • 37
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 7
    @Gremo You only get to pick one type to use for `_id` in your Mongoose schema. In the `"bla"` case you would use a type of `String` instead of the default `ObjectId` and you wouldn't need to add this check as anything can be cast to a string. – JohnnyHK Feb 18 '13 at 17:50
  • 3
    I understand, but I'd like to avoid this check. How can I create a new `ObjectId` from a given string (from the `GET` request) for passing it to the `findById` method? – gremo Feb 18 '13 at 17:58
  • 1
    @Gremo You can't. You can only construct ObjectIds from 24 hex character strings. – JohnnyHK Feb 18 '13 at 18:24
  • 2
    You can just use find({_id: yourId},...) to query for the document with that (unique) id. That, and JohnnyHK's answer to add _id to your schema (with your desired 'string' type) is the full solution to your problem. – Steve Hollasch Jan 03 '14 at 21:26
  • 3
    These days, 12 character strings can also be cast to an ObjectId. `ObjectId("000000000000") --> 303030303030303030303030` – Dan Ross Oct 15 '16 at 10:24
  • 1
    A small improvement of the regexp would be `/^[0-9a-f]{24}$/i` (make it case insensitive and remove the A-F) – Nicu Surdu Jul 18 '17 at 18:04
  • 1
    I Have a Same question, But I don't know how should I Solve? Can you Help me in This Issue? (https://github.com/Automattic/mongoose/issues/6151) what is fastest way with more performance to solve this? – Saeed Heidarizarei Feb 19 '18 at 09:13
  • 1
    @SedricHeidarizarei Consider posting that as a new question to SO. – JohnnyHK Feb 19 '18 at 14:12
  • 1
    Standard function exists now, check my answer - https://stackoverflow.com/a/61779949/11896312 – think-serious May 13 '20 at 16:33
  • Comprehensive error handling for MongoDB with Express and Node? Scouring the Internet, burning the midnight oil, getting rather frustrated. Thanks in advance for the snide comments. Onward. – MadHatter Aug 16 '20 at 03:36
84

Use existing functions for checking ObjectID.

var mongoose = require('mongoose');
mongoose.Types.ObjectId.isValid('your id here');
xpepermint
  • 35,055
  • 30
  • 109
  • 163
  • 24
    Careful using that method as it has the curious behavior of treating any 12-byte string as valid. So it even returns true for your `'your id here'` example. https://github.com/mongodb/js-bson/issues/106 – JohnnyHK Feb 08 '15 at 15:31
  • console.log("here"); let i = new mongoose.Types.ObjectId(userId.id); console.log("now here"); // this console not even printing – yogesh agrawal Jan 22 '18 at 15:42
43

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);
Ryan Dhungel
  • 3,637
  • 4
  • 19
  • 26
  • 5
    This worked for me. I am curious to the reason behind this error. Could you please explain how moving the route below the regular routes caused the error to go away? – Vishwak Oct 04 '19 at 13:41
  • 2
    This worked me as well. Looks like The /test/create satisfies this /test/:id with id=create. and string cannot be cast to_id. – kaila88 Mar 30 '20 at 22:02
  • 1
    @kaila88, your comment is quite logical, and the same way it is happening. – Vidyesh Ranade Dec 07 '20 at 07:55
  • 1
    This worked for me too, +1! It's so simple once you see it, but it was driving me nuts as the exception made me think there was something wrong with my DB doc IDs. :D – Cyril Graze Nov 05 '21 at 05:49
  • Dear reader, please read the answer above. It may also work for you as it did for me! – Gabriel Arghire May 17 '22 at 12:37
  • This worked for me as well but don't know the reason – Muhammad Mufeez Ahmed Aug 01 '22 at 02:52
  • 1
    @MuhammadMufeezAhmed it works because when you have params accepting route at top it satisfies criteria for other routes due to which request never reaches to the real route for example "/post/:postId" this route expects parameter after /post/, what if you try to enter /post/like It won't reach our written /post/like route instead it will match /post/:postid and you will see unwanted behaviour that's why always key route with parameters at bottom, so that it don't disturb regular routes I hope answers your question – Hanzla Habib Aug 04 '22 at 06:09
25

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

Harsh Verma
  • 253
  • 3
  • 5
  • Thanks @HarshVerma, this worked for my situation. I had a test route at the bottom of the file which kept returning a 500 error but after taking your advice and moving to the top of the file, it was successful. Can you explain why this happens? – IamToobDude Apr 23 '22 at 13:54
  • 3
    Hey @IamToobDude In this case generally what happens is that when you use the route that has param above the simple route then in that case any param you pass will get matched with that route For example /order/e5h57f here e5h57f is the id so it will match with /order/:id route in this case we dont see any problem but if the route was /order/me or /order/profile in this case also it will again match with /order/:id because while execution 'me' or 'profile' will be considered as :id in the /order/:id it just doesn't checks the usage while matching id param. – Harsh Verma Apr 23 '22 at 16:00
  • Ahh I see that makes a lot of sense thank you for the explanation – IamToobDude Apr 23 '22 at 16:41
  • This helped me to solve my problem which I spent many hours for debugging without any clue. Thanks buddy – Anu Apr 09 '23 at 14:09
24

I have the same issue I add
_id: String .in schema then it start work

s.babar
  • 408
  • 3
  • 10
14

Are you parsing that string as ObjectId?

Here in my application, what I do is:

ObjectId.fromString( myObjectIdString );
gustavohenke
  • 40,997
  • 14
  • 121
  • 129
  • Yes, you should, because you're querying an ObjectId type, so the cast is needed. – gustavohenke Feb 18 '13 at 17:33
  • 2
    Try `mongoose.Types.ObjectId`. – gustavohenke Feb 18 '13 at 17:59
  • 1
    Works, but I get "Invalid ObjectId" when passing "foo". So what's the point of creating an ObjectId from a string, if it may fail? – gremo Feb 18 '13 at 18:02
  • As per the MongoDB docs, ObjectIds must be 24 hexadecimal bytes only. – gustavohenke Feb 18 '13 at 18:07
  • Your mongoose model is expecting an valid MongoDB ObjectId. If you want to validate both ObjectIds and random strings, you should go with the mixed data type of mongoose models. – gustavohenke Feb 18 '13 at 18:11
  • I'm using a **UUID** generator which is outputting something like `b89e2e88-1630-4697-ae94-0ae537a38a44` -- which I use for client-side tracking and a list-id. My aim was to use `List.findOneAndUpdate({ _id: list.id }, ...)` -- how could I manage to look for this and create/store an object given that this should not be found? – Cody May 05 '15 at 00:59
  • 2
    `fromString` is not a function – WasiF Jan 30 '19 at 08:42
  • @WasiF yeah maybe not anymore, 6 years later... :| – gustavohenke Jan 31 '19 at 05:02
11

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;
Ericgit
  • 6,089
  • 2
  • 42
  • 53
  • 2022 this works. This helps my issue when the ID is not valid. Thank you –  Dec 24 '22 at 00:49
8

In my case, I had to add _id: Object into my Schema, and then everything worked fine.

Crowdpleasr
  • 3,574
  • 4
  • 21
  • 37
7

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

think-serious
  • 1,229
  • 2
  • 12
  • 27
6
 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

yogesh agrawal
  • 706
  • 7
  • 15
4

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.

Manil Malla
  • 293
  • 2
  • 6
4

All you have to do is change the parameter name "id" to "_id"

Iheb Saad
  • 327
  • 2
  • 6
3

You can also use ObjectId.isValid like the following :

if (!ObjectId.isValid(userId)) return Error({ status: 422 })
ZEE
  • 5,669
  • 4
  • 35
  • 53
2
//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
}
  • 1
    There are other answers that provide the OP's question, and they were posted many years ago. When posting an answer, please make sure you add either a new solution, or a substantially better explanation, especially when answering older questions. Code-only answers are considered low quality: make sure to provide an explanation what your code does and how it solves the problem. – help-info.de Apr 14 '19 at 09:17
2

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);
    }
});
Erons
  • 21
  • 4
2

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;
    }
  };
}
Denis Pshenov
  • 11,157
  • 6
  • 42
  • 42
2

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);
1

I went with an adaptation of the @gustavohenke solution, implementing cast ObjectId in a try-catch wrapped around the original code to leverage the failure of ObjectId casting as a validation method.

Controller.prototype.show = function(id, res) {
  try {
    var _id = mongoose.Types.ObjectId.fromString(id);



    // the original code stays the same, with _id instead of id:

    this.model.findById(_id, function(err, doc) {
      if (err) {
        throw err;
      }
      if (!doc) {
        res.send(404);
      }
      return res.send(doc);
    });



  } catch (err) {
    res.json(404, err);
  }
};
Charney Kaye
  • 3,667
  • 6
  • 41
  • 54
1

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);
        });

    });

});
YouneL
  • 8,152
  • 2
  • 28
  • 50
1

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.

Suman
  • 373
  • 4
  • 9
1

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
Ion Utale
  • 551
  • 1
  • 9
  • 22
1

ObjectId is composed of following things.

  1. a 4-byte value representing the seconds since the Unix epoch
  2. a 5-byte random value (Machine ID 3 bytes and Processor id 2 bytes)
  3. a 3-byte counter, starting with a random value.

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)
Sushil Kadu
  • 314
  • 3
  • 7
1

I was having problems with this and fixed doing mongoose.ObjectId(id) without Types

Juany
  • 133
  • 1
  • 5
1

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.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 24 '22 at 05:13
0

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

Community
  • 1
  • 1
Madhu Kumar
  • 395
  • 3
  • 11
0

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
WasiF
  • 26,101
  • 16
  • 120
  • 128
0

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.

Jim Bray
  • 758
  • 8
  • 12
0

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 }

0

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.
0

could happen if you are sending less or more then 24 characters string as id

aris
  • 409
  • 6
  • 8
0

if are using findByIdAndDelete method,then verify {_id:id} this object.

i.e this.model.findByIdAndDelete({_id:id}).exec()

-1

I fixed this problem changing the order of the routes.

  • This is not seems to be an answer. At best, it's a comment. – M S Feb 01 '20 at 09:33
  • This worked for me, I had 2 routes for blogs: '/blogs/create' and 'blogs/:id'. And the latter came first in the order of the routes. So when i went to the '/blogs/create' mongoose took the 'create' as an id – Wyrone Jul 04 '20 at 19:08