02: Mongoose Subdocuments & Discriminators
Interesting Mongoose options for MongoDB collections!
2/7/20246 min read


In a “greenfield” project where I contribute, we use MongoDB, mainly for it's future flexibility. This brand-new project contains a lot of unknowns! I’ll outline the problem we're solving.
Here, I assume you are familiar with databases and general JavaScript coding concepts. I will not cover how to connect to and use Mongoose alongside MongoDB outside of the Subdocuments and Discriminators options defined below. If you need an introduction, these references from freeCodeCamp are both excellent: Mongoose 101 or Introduction to Mongoose for MongoDB.
Our API proxy service will add a safety layer between the platform and a third-party vendor. Currently, we reach out directly to the vendor, causing multiple dependencies and a web of complicated actions which will need to be detangled and simplified.
Another long-term goal is to decouple our platform, allowing more vendor flexibility in the future. You never know when something will change with an API you don’t control. 😉
Do you want more straight to your inbox?
Subscribe to receive occassional blog posts!
Your contact information will never be sold.
Why MongoDB - or why a NoSQL database - you might ask?
Although there is a level of flexibility you can build and connect with relational databases and foreign keys, NoSQL databases are known for their flexible data structure capabilities. Ever-growing SQL tables leads to complicated queries and multiple join tables to access scattered data. Those queries can become expensive.
I mentioned the possibility of future third-party vendor(s). Preliminary research highlighted that the data across other possible vendors is just varied enough to need schema variation. Another win for MongoDB is that it can store multiple shapes of related data in the same collection! Don’t worry, we’ll cover this in more detail.




📒 Mongoose Documents
A Mongoose document is a mapping of a model. Your model should match the pre-defined schema(s). The Model class is a subclass of the Document class in the Mongoose implementation. When you use a Mongoose query, you are interacting with the Mongoose Document.
Let’s build on the above example as we go. Here’s the basic implementation of creating a Document with our above base model:
Although this might look close to what we did above, and they may look similar via MongoDB, Mongoose treats these differently.
A nested path like the above must be defined upon Document instantiation to be valid whereas in our earlier Subdocument example, we can set the `cook` field to `undefined` to start. We can then more easily alter the `cook` field when ready!
Subdocuments are nested Documents if you recall. Because of this, Mongoose gives each Subdocument an `_id` Document identifier making it searchable as a Subdocument or within it’s parent Document.
Although you can certainly use JavaScript methods on a subdocument or nested path object, nested paths do not allow you to take advantage of the built-in Mongoose methods to interact directly with a Document’s list of Subdocuments like our `cook` field.
If you need more clarification, I recommend referring to the Subdocument documentation.
🙈🙉🙊 Mongoose Discriminators
Discriminators essentially allow you to create schemas with varying object models to store within the same collection. This is an excellent option if you have a similar underlying schema structure, but you need slight differences.
When setting up a schema, an options object can be appended at the end as another parameter. In the case of Discriminators, a `discriminatorKey` in the options object is used with a value to define the Discriminator in the Documents. This key becomes searchable using the `__t` string path.
Once a Document is created with a Discriminator key, this key is not typically able to be updated by most methods. Though there are some update methods that use the `overwriteDiscriminatorKey` option to override this.
One other cool feature is that you can apply Discriminators to Subdocuments, too! Think about all the possibilities and flexibility your schema can have. 🤔
Let’s finish our example. First, we’ll revisit the base schema to add the `discriminatorKey` option:
📑 Mongoose Subdocuments
A Subdocument represents a Document embedded inside another Document. In other words, a Subdocument can also be defined as a schema within another schema.
As we work with Subdocuments, you might notice that they look pretty similar to Documents. They are! The main difference is a Subdocument will be added to a “top-level” or “parent” schema and can only be accessed and interacted with alongside the parent schema. Additionally, if you use any of the built-in Mongoose middleware or validation options on the Subdocument, this will be performed before the Document to ensure everything is in order before proceeding at the top-level.
Meaning, Subdocuments are not stored in a separate table and accessed with a join table like when using a relational database. Subdocuments cannot exist without their parent — MongoDB stores all of this data in a single Document.
(That said, MongoDB does have the capability of a “join” view if you’re interested.)
Okay, great, now that we have a pretty solid understanding of Subdocuments, let’s expand the `foodBaseModel` with a couple of ways to add Subdocuments!
Subdocuments and Nested Paths are Different
Before we move on, we should cover a common point of confusion. A nested path is not the same as a Subdocument, though they do look quite similar.
Try to keep an eye out for the subtle differences of this nested path example:
Then, we’ll create our separate schemas to add on top of our baseSchema with the `discriminatorKey`:
And finally we create the new Documents (including the Subdocuments) for each Discriminator type:
🛠️ When and how to use these tools
Whether you use these options in a Mongoose project has the same answer heard frequently within the world of software engineering: “It depends.”
Should all Documents take advantage of Subdocuments if they have an associative object structure? No. Subdocuments are most useful for a specific schema to use and enforce for a nested object, the nested object should be searchable, or the option to leave the nested object structure off of the Document until a later time without having to define it up front could be used strategically.
And Discriminators? Yup - they’re not for every situation! I find Discriminators are best for unique situations. It’s not common to need slight variants on schemas. But, as I mentioned earlier, our case of slightly different data shapes for third-party vendor data is a great scenario for using the Discriminator option in Mongoose.
Personally, I’ve found that Mongoose has made development with MongoDB easy and enjoyable. The library is well-documented and both MongoDB and Mongoose have a great community with plenty of examples to draw from.
I hope you found the usage of Subdocuments and Discriminators by this non-relational database as interesting as I did!
Sources:
MongoDB Article: Getting Started with MongoDB and Mongoose
Mongoose Documentation: Documents | Subdocuments | Discriminators
Introduction to Mongoose for MongoDB and Mongoose 101 from freeCodeCamp
BONUS Recommendation: MongoDB Podcast
…and that’s the perfect segway to the crux of this topic.
In this post, we’ll cover:
❓ What is Mongoose?
📒 Mongoose Documents
📑 Mongoose Subdocuments
🙈🙉🙊 Mongoose Discriminators
🛠️ When and how to use these tools
❓ What is Mongoose?
To more easily work with MongoDB, developers frequently use Mongoose. It’s not the only tool, and a tool is not required to use MongoDB, but tools provide structure and fluidity in my experience.
Mongoose is a popular third-party JavaScript library for Node.js. It helps to model, validate, and manipulate data along with plenty of other interesting capabilities. Mongoose is an ODM, or Object Data Modeling, library.
Mongoose provides more structure to developer interactions with MongoDB. The schema allows you to define the shape of your data and its expected types as well as additional options like default values, designated uniqueness, or indexing for example.
The model, on the other hand, applies your schema structure to each of the MongoDB documents. Models are then used for “CRUD” database actions on the records: creating, reading, updating, and deleting. — View the full list of Mongoose queries available.
To ensure our mental model is aligned, here’s an example of a base schema in JavaScript we will use to define a model we can work with:
Thanks so much for reading! ✨
Did I miss anything, or would you like to add anything?
Let me know!
I appreciate constructive feedback so we can all learn together. 🙌
Connect
You can find Mindi Weik on these platforms:
mindi@wip-podcast.com
© 2025. All rights reserved.
![[WIP] Podcast logo](https://assets.zyrosite.com/cdn-cgi/image/format=auto,w=375,fit=crop,q=95/Yan01MjyJEH16nVN/black-background-AMqDQ54p9xtwjbqg.png)