Search code examples
mongodbmongoose

ValidationError: Cast to ObjectId failed for value 'X' (type string) at path '_id' - MongoDB


I am trying to seed my mongodb database and I am running into this Validation error.

the error:

ValidationError: food validation failed: _id: Cast to ObjectId failed for value "1" (type string) at path "_id" because of "BSONError"
    at model.Document.invalidate (/backend/node_modules/mongoose/lib/document.js:3157:32)
    at model.$set (/backend/node_modules/mongoose/lib/document.js:1453:12)
    at model.set (/backend/node_modules/mongoose/lib/helpers/document/compile.js:205:19)
    at model.idSetter (/backend/node_modules/mongoose/lib/helpers/schema/idGetter.js:41:12)
    at VirtualType.applySetters (/backend/node_modules/mongoose/lib/virtualtype.js:166:16)
    at model.$set (/backend/node_modules/mongoose/lib/document.js:1271:12)
    at model.$set (/backend/node_modules/mongoose/lib/document.js:1113:16)
    at model.Document (/backend/node_modules/mongoose/lib/document.js:164:12)
    at model.Model (/backend/node_modules/mongoose/lib/model.js:122:12)
    at new model (/backend/node_modules/mongoose/lib/model.js:4682:15)

my model.ts looks like:


export interface Food {
    id: string;
    name: string;
    price: number;
    tags: string[];
    favorite: boolean;
    stars: number;
    imageUrl: string;
    origins: string[];
    cookTime: string;
    url: string;
}

export const FoodSchema = new Schema<Food>(
    {
        name: {type: String, required: true},
        price: {type: Number, required: true},
        tags: {type: [String], required: true},
        favorite: {type: Boolean, default: false},
        stars: {type: Number, required: true},
        imageUrl: {type: String, required: true},
        origins: {type: [String], required: true},
        cookTime: {type: String, required: true},
        url: {type: String, required: true},
    }, {
        toJSON: {
            virtuals: true
        },
        toObject: {
            virtuals: true
        },
        timestamps: true
    }
);

export const FoodModel = model<Food>('Food', FoodSchema);

and my seed api looks like:

router.get("/seed", asynceHandler(
    async (req,res)=>{
        const foodsCount = await FoodModel.countDocuments();
        if(foodsCount>0){
            res.send("Seed is already done");
            return;
        }
        await FoodModel.create(sample_foods);
        res.send("Seed is done")
    }
))

What am I doing wrong? I am a newbie to Mongodb and trying to learn it with the help of a tutorial.


Solution

  • id: '1' is not a valid id. The valid Id can be a 24 character hex string, 12 byte binary Buffer, or a number. Mongoose cast the id: '1' to an ObjectId failed, that's why you got the error.

    How does cast work? Mongoose will try to create the ObjectId using new bson.ObjectId('1'), in the ObjectId constructor, it will validate the input id and throw a BSONError

    You have three choices:

    If you want to use the _id Mongoose added to the document, do:

    • create the ObjectId explicitly using new mongoose.Types.ObjectId()

    • Let the mongoose create ObjectId for you. You don't need to pass the id field to the Food.create() method.

    If you want to create an id field by yourself, do:

    • Declare an id field with a String type in the schema. But you have to maintain its uniqueness with a unique indexes.

    E.g.

    import mongoose from 'mongoose';
    import { config } from '../../config';
    
    mongoose.set('debug', true);
    console.log(mongoose.version);
    
    const FoodSchema = new mongoose.Schema({
        // id: String
        name: String,
    });
    const Food = mongoose.model('Food', FoodSchema);
    
    (async function main() {
        try {
            await mongoose.connect(config.MONGODB_URI);
            // seed
            const sample_foods = {
                id: '1',
                name: 'Pizza Pepperoni',
            };
            // create ObjectId explicitly
            // await Food.create({ ...sample_foods, id: new mongoose.Types.ObjectId() });
    
            // create ObjectId implicitly
            const { id, ...rest } = sample_foods;
            await Food.create(rest);
    
            // declare an id field with the String type, then `id: '1'` is ok.
            // await Food.create(sample_foods);
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();