Define MongoDB _id-Type as String

Hi there! I have the following MongoDB-document:

{
    _id: 'EXISTING_ID'
}

and I want to use the native MongoDB-driver for node.js to delete this document. I tried doing so using the following code:

import { MongoClient } from "mongodb";

const dbUrl = "mongodb://localhost:27017/";

export const deleteDocumentWithId = (id: ObjectId) => {
  return MongoClient.connect(dbUrl, (err, db) => {
    if (err) {
      throw err;
    }

    const dbo = db.db("my-db");

    dbo.collection("my-collection").deleteOne({ _id: id }, (err, obj) => {
      if (err) {
        throw err;
      }
      db.close();
    });
  });
};

deleteDocumentWithId("EXISTING_ID");

This will throw a compiler-error, because id is of type string, rather than ObjectId.

How can I tell the MongoDB-driver to use type string for _id here?

As I can’t seem to find the “edit”-button, I’m writing this as a comment: The function definition above should be

export const deleteDocumentWithId = (id: string) => {
  // ...
}

Maybe not directly relevant to your question, but Mongoose does not have this problem. We pass strings to Mongoose for ID and it works like charm.

Hi @aerabi and thanks for your reply! :slight_smile: You’re right in that this doesn’t actually answer the original question, however I’ll try out Mongoose for my use-case and see if it provides a valid workaround. Thanks again! I’ll report back here.

Although I’d really like to know, if this functionality does actually exist with the native driver.

I’m glad to help. I’ll try to investigate the native driver more in the meantime. But, perhaps it likes things more explicitly, e.g.

collection.deleteOne({ _id: ObjectID.createFromHexString(id) })

In contrast, Mongoose has a more handy function, called deleteById:

model.deleteById(id)

P.S: I personally prefer the promise API over the callback API. Using the callbacks, the code will get hard to read really fast.

Do you mean findByIdAndDelete by any chance?

Also createFromHexString didn’t work for me, which makes sense because EXISTING_ID is not a hex-string.

This issue has nothing to do with the mongodb driver but has everything to do with JS type checking. You might be using TypeScript rather than native JS.

Actually, it is has everything to do with the way you are coding your JS function

deleteDocumentWithId 

When you declare (id : ObjectId) you are asking JS to make sure the function is called only with ObjectId. You are on the right track when you change it to (id : string). Now you tell JS that you only want to call your function with string. At this point the MongoDB native driver API is not in play yet. This MongoDB API is really

If you want to indicate that you can pass anything as the type for your id simple do

export const deleteDocumentWithId = (id) => {

That is, remove the type specification.

By the way you are also doing something very very wrong in your deleteDocumentWithId. You call MongoClient.connect(). You do not want to connect every time you delete.

I strongly suggest that you read https://www.typescriptlang.org/ and also take M220JS from MongoDB University.

And about your P.S.: I got the function working and simplified it to this:

export const deleteDocumentWithId = async (id: string) => {
  await mongoose.connect(dbUrl);
  await OrderModel.findByIdAndDelete(id);
  await mongoose.disconnect();
};

Does this resemble how you normally use it?

1 Like

As you can see from my first reply, I had a typo in the original question and couldn’t seem to find the “edit” button (if there is any). id should in fact be a string. However, when I call deleteOne({_id: "EXISTING_ID"}...) the MongoDB-driver requires _id to be of type ObjectId rather than type string, which is why this topic even exists.

Please prove me wrong! I’m already searching for a solution since yesterday :smiley:

As to when to actually connect and disconnect: My impression was, that every connect should have a matching disconnect. My actual use-case is for end-to-end testing. The function I showed you will be eased after each test execution. Connecting and disconnecting surely lowers performance, however I don’t see a good way to implement this just yet.

It is your data that mandates which value you pass to deleteOne. If your _id is a string you must pass a string, if your _id is a number you must pass a number and if it is an ObjectId you must pass an ObjectId.

I will try to illustrate that with an example:

// Starting with the collection
c.find()
[
  { _id: 1 },
  { _id: '1' },
  { _id: ObjectId("61b9fca97bbec1567eb0bb9c") },
  { _id: ISODate("2021-12-15T14:34:57.380Z") },
  { _id: { a: 1, b: 2 } },
  { _id: { b: 2, a: 1 } },
  { _id: 1.1 }
]

// _id has many different data types.  Of particular interest, it is where _id is an object both a and b fields
// of the _id object have the same values yet the _id is different, because the order of the fields are different

// The following will find { _id:1 } but not { _id:"1" } because the types are different
c.find( { _id : 1 } )

// For _id: ObjectId("61b9fca97bbec1567eb0bb9c") you must pass an ObjectId because the
// string 61b9fca97bbec1567eb0bb9c will not work because the types are different.
c.find( { _id : ObjectId("61b9fca97bbec1567eb0bb9c") } )
[ { _id: ObjectId("61b9fca97bbec1567eb0bb9c") } ]
// but with the string it finds nothing.  (I used count() because it is hard to show the lack of output) 
c.find( { _id : "61b9fca97bbec1567eb0bb9c" } ).count()
0
// As for the object one, only the one with the matching order and values is found
c.find( { _id: { a: 1, b: 2 } } )
[ { _id: { a: 1, b: 2 } } ]

I used find() rather than findOne() or deleteOne() because I did not want to remove my data and wanted to show that the type is important.

True. It is usually done outside the main loop of your application.

The actual issue here is, that I didn’t type collection correctly! The corrected function would look like this:

import { MongoClient } from "mongodb";

const dbUrl = "mongodb://localhost:27017/";

export const deleteDocumentWithId = (id: ObjectId) => {
  return MongoClient.connect(dbUrl, (err, db) => {
    if (err) {
      throw err;
    }

    const dbo = db.db("my-db");

    dbo.collection<DocumentType>("my-collection").deleteOne({ _id: id }, (err, obj) => {
    //                      ^^^ Note this type-definition!
      if (err) {
        throw err;
      }
      db.close();
    });
  });
};

deleteDocumentWithId("EXISTING_ID");

It actually would have sufficed to type collection like this:

dbo.collection<{ _id: string }>("my-collection").deleteOne({ _id: id }, (err, obj) => {

I got this answer off of my own related questions on Reddit and Stackoverflow. I didn’t find any documentation saying, that collection is generic though. This might be an issue for more users.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.