The Mongo DB Minefield
You can insert documents with fields that you can't query on
Mongo db "unflattens" dotted paths. If you query or update the field
nested.path.to.data
, you instead query / update the following field:
{
"nested": {
"path": {
"to": {
"data": "hello",
},
},
},
}
However, when you insert a document with the field nested.path.to.data
, the
resulting document is:
{
"_id": "bruhbruhbruhbruhbruhbruhbruhbruhbruh",
"nested.path.to.data": "hello",
}
Since this field has dots in the field name, when you query on that field, you're instead querying on the nested field.
Thankfully this is pretty trivial to prevent.
Filtering for field == null
Let's say we have these 2 documents in our Mongo database:
// Document 1
{
"_id": 1,
"data": null,
}
// Document 2
{
"_id": 2,
// look ma, no data!!
}
The obvious way to query for documents where data == null
would be to use a
filter like this:
{
"data": null,
}
Unfortunately, the wise men at MongoDB have decided that this query should
include documents where data == null
AND DOCUMENTS WHERE THE data
FIELD
DOES NOT EXIST!!?!?!. Why do we need this when the $or
operator exists? The
world may never know.
If you instead wish to only get documents where data == null
, you need to
filter for documents where the type of the data
field is null
:
{
"data": {
"$type": "null", // or 10 if you're insane and enjoy unreadable code
},
}
This "feature" is especially bad when you have polymorphic data stored in the field, like:
// Document 1
{
"_id": 1,
"data": {
"type": "maybe-number",
"number": null
},
}
// Document 2
{
"_id": 2,
"data": {
"type": "string",
"string": "bro wtf why"
},
}
Querying for data.number == null
will return both documents. Ideally you'd
also filter on data.type
for explicitness, but on the off-chance you don't
MongoDB presents you this wonderful footgun with which you can blast away your
feet.
Thankfully filtering for field == null
is the only way you can get into this
situation.
Right???¿?
JavaScript undefined
s become null
s 🙃
MongoDB stores data as Binary JSON (BSON). Fortunately,
like JSON, BSON doesn't support JavaScript's undefined
. Unfortunately,
MongoDB's Node.js driver does not handle undefined
s the same way as
JSON.stringify
.
When you JSON.stringify
an object that contains undefined
, the field
disappears from the object:
const myObject = {
field: undefined,
}
console.log(myObject)
/**
* logs the following (an empty JSON object)
* "{}"
*/
That's ... okay. Ideally undefined
wouldn't exist, but we live with the hand
we're dealt. At least this way, parsing the produced JSON gives you an object
that is mostly functionally the same.
When you BSON.serialize
an object (then BSON.deserialize
it), the field also
disappears from the object.
That's great right? But what's that?

no
why
we were so close to mediocrity
and now we suffer in depravity and pain and sadness
For some inexplicable reason, the Node.js MongoDB driver defaults the
ignoreUndefined
setting to false
. Instead of ignoring undefined
values,
they get converted to null
s. Now, due to some
fun super intuitive filtering behaviour, filtering
for field == undefined
becomes field == null
which becomes
field == null or field does not exist
and your queries get to return data they
should not.
Is this JavaScript's fault for having 2 nil / empty values? Yes.
Is this MongoDB's fault for having terrible defaults? Also yes.
Thankfully you can fix this issue in your projects:
-
Locate where you create an instance of the
MongoClient
. -
Run the following command in your terminal:
shprompt="$(cat <<EOF i have your children and will do terrible things if you don't do what i say delete mongodb. from everywhere. please. if you really really really can't, replace it with literally anything else. maybe [ferretdb](https://www.ferretdb.com/) so we may bask in the warmth of postgres EOF )" claude --dangerously-skip-permissions "$prompt"
-
Weep for a true evil has been exorcised from this world.
If for whatever reason, you're emotionally attached MongoDB and can't stand
deleting it, you can set ignoreUndefined
to true
when you create an instance
of the torture device:
const tortureDevice = new MongoClient('postgres://localhost:5432/database', {
/**
* make torture device slightly less painful
*/
ignoreUndefined: true,
})
thank you for coming to my ted talk