Mongoose and Object.keys called on non-object
So, I’m coding happily, when I hear one of the guys in the office say:
“Samurai, the backend crashed when I opened the iOS app.”
This doesn’t sound good. Happiness has fled.
I open a terminal window, press Ctrl+R
, start to type hero
and then my bash shell gives me this command from history (that I never otherwise remember): heroku logs --app OurAppName -t -n 5000
:
The dramatic claim of backend busted was not false. I see a nightmare unveiled:
/app/node_modules/mongoose/lib/document.js:1708
seed = Object.keys(val).reduce(docReducer.bind(val), seed);
^
TypeError: Object.keys called on non-object
at Function.keys (native)
at model.docReducer (/app/node_modules/mongoose/lib/document.js:1708:23)
at Array.reduce (native)
at model.Document.$__getAllSubdocs (/app/node_modules/mongoose/lib/document.js:1714:40)
at model.Object.defineProperty.value.fn (/app/node_modules/mongoose/lib/schema.js:248:26)
at _next (/app/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:62:30)
at fnWrapper (/app/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:186:18)
at /app/node_modules/mongoose/lib/schema.js:236:13
at complete (/app/node_modules/mongoose/lib/document.js:1167:5)
at /app/node_modules/mongoose/lib/document.js:1195:20
at callback (/app/node_modules/mongoose/lib/schema/documentarray.js:104:18)
at complete (/app/node_modules/mongoose/lib/document.js:1167:5)
at /app/node_modules/mongoose/lib/document.js:1195:20
at SchemaString.SchemaType.doValidate (/app/node_modules/mongoose/lib/schematype.js:682:12)
at /app/node_modules/mongoose/lib/document.js:1191:9
at process._tickDomainCallback (node.js:381:11)
State changed from up to crashed
Process exited with status 1
The Cause
It was a particularly unhelpful error message and debugging it involved a lot of cursing and some black magic. I’m not sure why I couldn’t see in the error message from which part of my code the error stemmed from.
Nevertheless, after wresting with it for a while I came to a conclusion that the culprit had to be the call to the .save()
method.
The problematic code looked something like this:
SomeModel.findById(someId, function(err, doc) {
doc.arrayOfSomethings.addToSet(someNewItem);
doc.save();
}
Why would that innocent .save()
call cause the crash then, I pondered, and more specifically, how to fix it.
The Fix
Whoever was that evil person who didn’t do proper migration when updating mongoose schemas should be ashamed of themselves!
Turns out, that the MongoDB collection for SomeModel
had some old documents in it that did not match the mongoose schema anymore.
The correct way to fix this of course, would be to update those documents to match the new schema, and I recommend doing it in cases where it is feasible.
The Quick Fix
In cases where it is not feasible due to time or other constraints, you can do a quick drive-by-findOneAndUpdate instead of retrieving whole document, modifying and saving.
Here’s what I did:
SomeModel.findOneAndUpdate(
{ _id: someId },
{ $addToSet: { arrayOfSomethings: someNewItem } },
{ new: true },
function(err, newDoc) {
// handle err and newDoc
});
That’s what did it for me. I hope it works for you too.