BlogNode.js

How to convert strings to ObjectIds in MongoDB

Written by Codemzy on December 11th, 2024

ObjectId's are not strings in MongoDB, but they might get converted to strings when you send them to the browser. Here's how to convert them back, and fix any ObjectId strings saved in your database.

ObjectIds are funny things. They might get sent from your server to the client as strings, but they are not strings. So at some stage, you will probably need to convert ObjectIds to and from strings.

Forgetting to convert a string to an ObjectId can sometimes be the cause of issues matching documents. If you look the _id up as a string instead of an ObjectId, the document won't match.

So if you get an ObjectId in string format, for example from an API call, you'll need to convert it to an ObjectId before you send it off for matches in MongoDB.

In this blog post, we will look at:

  • How to convert a string to an ObjectId in your database query
    ObjectId.createFromHexString(string)
  • How to convert a string saved in your database to an ObjectId
    { $toObjectId: "$field" }

Here we go...

Convert string to ObjectId in query

Let's say a user wants to look at another user from their team. They click on a user from a list of users, and the front end sends the back end the ID of that user.

We have a collection of users like this:

{
  _id: ObjectId("64b9523b35795e90949872a1"),
  name: "codemzy",
}

But we won't get an ObjectId back from the front end. We will get the user ID as a string.

app.get('/orders/:id', (req, res) => {
  let orderId = req.params.id;
});

And looking up the order by the ID won't work because the string won't match the ObjectId. We first need to convert the string to an ObjectId. We can do this with ObjectId.createFromHexString().

const { ObjectId } = require('mongodb');

app.get('/orders/:id', (req, res) => {
  let orderId = ObjectId.createFromHexString(req.params.id);
  const order = await db.collection("orders").findOne({ _id: orderId });
});

Convert stored strings to ObjectIds in MongoDB

Sometimes, I'll have a collection that stores an ObjectId as a string. For example, I'll have a collection of users - like this:

{
  _id: ObjectId("64b9523b35795e90949872a1"),
  name: "codemzy",
}

But in another collection, for example, orders, the user reference is a string.

{
  _id: ObjectId("5a934e000102030405000001"),
  user: "64b9523b35795e90949872a1",
  orderDate: ISODate("2024-12-11T14:10:30Z"),
  // ...
}

This doesn't cause too many issues for a simple query, like looking up a user's orders, I can query by the user string:

const orders = await db.collection("orders").find({ user: user._id.toString() });

If you're looking up multiple documents, don't forget to add pagination with skip and limit!

But it can make things a little tricky if I need to do a more complex query, like looking up an order and finding the user by ID afterwards.

I could either do two separate queries (converting the string to an ObjectId before the user query as we did in the section above) or in a single aggregation, I'd need to convert that string into an ObjectId first - which adds an extra step to my aggregation.

let order = await db.collection('orders').aggregate([ 
  { $match: { _id: ObjectId.createFromHexString(order) } },
  { $addFields: { "userId": { $toObjectId: "$user" } } },
  { $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "user" } },
  { $set: { user: { $first: "$user" }, } }, // one-to-one join so only one array element
]).toArray().then((results) => {
  return results[0]; // aggregation returns array - first (only) item is order
});

In { $addFields: { "userId": { $toObjectId: "$user" } } }, I'm adding the ObjectId so I can $lookup the user from the users collection.

ObjectIds are more compact and better for indexes compared to their string representation, so it's always better to store ObjectIds as ObjectIds in MongoDB. So when I came across this issue in a database I was working on, I decided to go ahead and convert all the strings into ObjectIds.

With an update operation with an aggregation pipeline, we can use $toObjectId to convert that user string into an ObjectId.

db.collection('orders').update({},
[
  {
    $set: {
      user: {
        $toObjectId: "$user"
      }
    }
  }
]);

See this update happen in the MongoDB playground!

Now the user field is an ObjectId instead of a string!

[
  {
    "_id": ObjectId("5a934e000102030405000001"),
    "orderDate": ISODate("2024-12-11T14:10:30Z"),
    "user": ObjectId("64b9523b35795e90949872a1")
  }
]