skip to Main Content

I have some Mongoose code which fails running on NodeJS.

The relevant line is:

// get data from database
const existingUser = await User.findOne({ userId });

userId is a "number" type.

console.log(typeof userId); // -> number
console.log(userId);        // -> 1

This value is obtained from the body of a http request where the body is encoded as JSON.

Therefore I am not surprised to find that the type is "number" because all JSON numbers are the same (floating point, no integers) however I am surprised to find that this causes an error and Mongoose cannot convert the number to an integer type.

Here the request is received by a post endpoint defined in express.


app.post("/test", async (request, response) => {
    // get user id from body of request

    console.log(request.body); // -> { userId: 1 }

    const userId = request.body.userId;

    // ...

How can I fix this?

The exact error is:

CastError: Cast to ObjectId failed for value "1" (type number) at path "userId" for model "User"

reason: BSONError: input must be a 24 character hex string, 12 byte Uint8Array, or an integer

Edit: Schema, on request

const user = new mongoose.Schema({
  userId: {
    type: mongoose.Types.ObjectId,
    unique: true,
    required: true,
  },
});

2

Answers


  1. Chosen as BEST ANSWER

    For some reason it requires an explicit cast, or creation of an ObjectId type:

    const existingUser = await User.findOne({
      userId: new mongoose.Types.ObjectId(userId),
    });
    

  2. When you query a collection on a property that is of type ObjectId mongoose will internally cast your query parameter value to an ObjectId in order to do an equality check. This is very handy.

    However, a great source of confusion is that an ObjectId such as ObjectId("6578c06c3f3559a5e969fc88") is made up of:

    A 4-byte timestamp, representing the ObjectId’s creation, measured in seconds since the Unix epoch.

    [6578c06c] 3f3559a5e969fc88

    A 5-byte random value generated once per process. This random value is unique to the machine and process.

    6578c06c [3f3559a5e9] 69fc88

    A 3-byte incrementing counter, initialized to a random value.

    6578c06c3f3559a5e9 [69fc88]

    In your case you have a number userId where

    request.body.userId === 1 // true
    

    Mongoose sees that User.userId is an ObjectId and tries to cast your userId value to an ObjectId and can’t so it throws a Cast to ObjectId failed error.

    What you have done as a workaround is use the mongoose.Types.ObjectId() function to make a new ObjectId and then use that value to find a match. However that function not only takes a valid 24 character hexadecimal string but it also takes an integer.

    You think you have converted your userId value to an ObjectId that will match a document but all you have done is create an ObjectId that is 1 second after the Unix Epoch timestamp combined with the 5-byte random value and the 3-byte incrementing counter. To illustrate:

    console.log(new mongoose.Types.ObjectId(1));
    // ObjectId('0000000108423469423cf5f1')
    
    console.log(new mongoose.Types.ObjectId(1));
    // ObjectId('0000000108423469423cf5f5')
    
    console.log(new mongoose.Types.ObjectId(1));
    // ObjectId('0000000108423469423cf5f9')
    

    Your value of 1 has created the 00000001 part of the new ObjectId. You can run it a million times and you will always get the same 00000001.

    I am fairly certain you won’t have any documents that were created at 00:00:01 UTC on 1 January 1970 so while your query won’t error it won’t find any documents.

    All you need to do is allow your front-end to send a valid string form of an ObjectId such as { userId : "6578c06c3f3559a5e969fc88" } and mongoose will do the rest.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search