How to Use Discriminator in Mongoose for Schema Inheritance
In Mongoose,
discriminator allows you to create multiple models with a shared base schema but different specialized schemas. You define a base schema, then use Model.discriminator() to create child models that inherit the base schema and add their own fields.Syntax
The discriminator method is called on a base model to create a new model with an extended schema. It takes two main arguments: the name of the new model and the schema that extends the base schema.
- Base Schema: The common fields shared by all models.
- Discriminator Name: A string to identify the child model.
- Discriminator Schema: The schema with additional fields specific to the child model.
javascript
const baseSchema = new mongoose.Schema({ commonField: String }); const BaseModel = mongoose.model('Base', baseSchema); const childSchema = new mongoose.Schema({ specificField: Number }); const ChildModel = BaseModel.discriminator('Child', childSchema);
Example
This example shows how to create a base Event model and two discriminators: ClickedLinkEvent and SignedUpEvent. Each child model has its own specific fields but shares the base schema fields.
javascript
const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/testdb', { useNewUrlParser: true, useUnifiedTopology: true }); const eventSchema = new mongoose.Schema({ timestamp: Date, userId: String }); const Event = mongoose.model('Event', eventSchema); const clickedLinkSchema = new mongoose.Schema({ url: String }); const signedUpSchema = new mongoose.Schema({ plan: String }); const ClickedLinkEvent = Event.discriminator('ClickedLink', clickedLinkSchema); const SignedUpEvent = Event.discriminator('SignedUp', signedUpSchema); async function run() { await mongoose.connection.dropDatabase(); const click = new ClickedLinkEvent({ timestamp: new Date(), userId: 'user123', url: 'https://example.com' }); const signup = new SignedUpEvent({ timestamp: new Date(), userId: 'user456', plan: 'Pro' }); await click.save(); await signup.save(); const events = await Event.find(); console.log(events); mongoose.connection.close(); } run();
Output
[
{
_id: ObjectId("..."),
timestamp: 2024-06-01T00:00:00.000Z,
userId: 'user123',
url: 'https://example.com',
__t: 'ClickedLink',
__v: 0
},
{
_id: ObjectId("..."),
timestamp: 2024-06-01T00:00:00.000Z,
userId: 'user456',
plan: 'Pro',
__t: 'SignedUp',
__v: 0
}
]
Common Pitfalls
- Not defining a base model: You must create a base model before using
discriminator. - Schema conflicts: Avoid overlapping field names in base and discriminator schemas.
- Missing
__tfield: Mongoose adds a__tfield to identify the discriminator type; do not remove or override it. - Using discriminators with different collections: Discriminators share the same MongoDB collection as the base model.
javascript
/* Wrong: Using discriminator without base model */ // const Child = mongoose.model('Child', new mongoose.Schema({ field: String })); // const Wrong = Child.discriminator('Wrong', new mongoose.Schema({ extra: Number })); /* Right: Define base model first */ const Base = mongoose.model('Base', new mongoose.Schema({ field: String })); const Correct = Base.discriminator('Correct', new mongoose.Schema({ extra: Number }));
Quick Reference
Discriminator Cheat Sheet:
| Step | Action |
|---|---|
| 1 | Create base schema and model |
| 2 | Define child schemas with extra fields |
| 3 | Use BaseModel.discriminator(name, schema) to create child models |
| 4 | Save and query using base or child models |
Key Takeaways
Discriminator lets you create related models sharing a base schema with extra fields.
Always define a base model before creating discriminators.
Discriminators share the same MongoDB collection as the base model.
Mongoose adds a __t field to track the discriminator type automatically.
Avoid field name conflicts between base and discriminator schemas.