View contents button
Share buttons
‘Model.find’ is not a function — Cyclic Dependency between Models and How to Fix It
Posted on 23rd Aug, 2021
Last updated 3 years ago

Ever been in that state of flow when your productivity is at a God-level, and you’re writing your Mongoose models, and finally binding everything up with Express, only to test your new APIs and find your ‘frenemy’ debugger throw up Hey there love! It seems 'MyPreciousModel.findOne' is not a function., and you spend your next 24 hours questioning your choice to become a developer? Fret not reader, for I have come to your rescue (after being bonked similarly for 16 hours)!

Let’s look at the scenario

Say you have two models, ModelOne and ModelTwo . Each one of them contain their own schema (irrelevant here, so I am excluding this in this example), complete with their own static and instance methods. They also occasionally call a few of the other model’s methods.

Next, you write your APIs in Express, and having imported the models, you invoke their methods in the API routes. Finally, you test your APIs, only to receive blank responses every time! What went wrong?

Then you fire up your debugger, setting breakpoints at every line, and after hours of painstaking debugging, you find that it’s not the API itself, it’s the model… the model is undefined !

So you look at your exports, find that the model has been exported correctly. Then, you look at your routers, see that the model was imported correctly as well. “What the hell?”, you think, “where’s my model? Arghhhhh!”.

Let’s take a step back and ponder, “How does Mongoose know about my Models in the first place?”.

How Mongoose knows your Models

Take a look at this extract from their documentation —

Models are fancy constructors compiled from Schema definitions. ... When you call mongoose.model() on a schema, Mongoose compiles a model for you. ... Note: The .model() function makes a copy of schema. Make sure that you've added everything you want to schema, including hooks, before calling .model()!

In essence, whatever schema you write, Mongoose compiles it into a Model, then makes it ready for you access it.

“But how does it solve my problem, you idiot?” you ask? Well, I extracted the above lines from their docs for a reason. Before moving on to the next section where I explain the cause of the aforementioned error and the fix, read the above extract yourself. If you can find the hints, it will all click, and you will feel supreme happiness (Well, at least I did when I saw it).

Cyclic Dependency: “Allow me to introduce myself!” *slyly smiles

If you didn’t get the hint, let’s think about the above extract. It says that a schema is compiled into a model, right? Also, any stuff you want to add to your schema, you need to do that before you compile it into a model, right? Makes sense.

But now, think about a scenario when there’s not one, but two schemas! Two schemas, where either one of them calls a method of the other, or both of them calling each others’ methods. You define the schemas individually, compile them to models individually. So why’s the models undefined at runtime? Ready for the clicking moment?

Node.js is single-threaded, is it not? Therefore, it can only compile one schema at a time, yes? That means, if modelA calls modelB ‘s methods (in the source code), but modelA gets compiled before modelB , then is modelB really available to modelA ?

Absolutely not! How can it be? ModelB does not even exist when ModelA is being compiled! So even though you have all of your imports/exports correctly, an undefined model gets bound to your modelA !

That clicking moment!
That clicking moment!

This is called a ‘cyclic dependency’ btw.

So, what’s the fix?

Now that we know the problem, we can find the solution very easily. It’s a one-liner change!

We know that the order in which models get compiled causes this problem, yes? So, what if there’s a way to not bind compiled models to schema methods, but rather runtime references to them?

There indeed exists a way, and that is the solution. Here’s an example that would make this clear to you:

This is the wrong way!

1schemaOne.methods.someMethod = () => {await modelTwo.anotherMethod() // modelTwo would be undefined
2...}

This is the right way! Pretty neat, huh?

1schemaOne.methods.someMethod = () => {await mongoose.model('modelTwo').anotherMethod() // This dynamically gets you modelTwo at runtime.
2...}
Dayumm!
Dayumm!

An ending note

Now that you know the fix, you can breathe easy! The models are no longer directly dependant. You can now have hundreds of inter-model dependencies, and everything would work just fine! (But don’t do it though. A hundred inter-model dependencies? Come on!)

This ‘bug’ is neither mentioned in the documentation, nor is it that well explained anywhere else. I hope the Mongoose devs someday mention this behaviour in the documentation, or better yet, internally substitute model variables with their equivalent dynamic references during the compilation process.

See you in the next post!

About me

Hey there! I am Sohail, and I am a Frontend web developer, specializing in the React.js ecosystem. I love learning, and creating content to help the community.

Have something interesting to say? Hit me up!

Comments
You may like