Currently I have two almost identical schemas:
var userSchema = mongoose.Schema({
email: {type: String, unique: true, required: true, validate: emailVal
All of these answers seem rather needlessly complicated, with extension helper functions or extend methods applied to the schema's or using plugins/discriminators. I've used the following solution instead which is simple, clean and easy to work with. It defines a blueprint for the base schema, and then the actual schema's are built using the blueprint:
foo.blueprint.js
module.exports = {
schema: {
foo: String,
bar: Number,
},
methods: {
fooBar() {
return 42;
},
}
};
foo.schema.js
const {schema, methods} = require('./foo.blueprint');
const {Schema} = require('mongoose');
const FooSchema = new Schema(foo);
Object.assign(FooSchema.methods, methods);
module.exports = FooSchema;
bar.schema.js
const {schema, methods} = require('./foo.blueprint');
const {Schema} = require('mongoose');
const BarSchema = new Schema(Object.assign({}, schema, {
bar: String,
baz: Boolean,
}));
Object.assign(BarSchema.methods, methods);
module.exports = BarSchema;
You can use the blueprint for the original schema as is, and using Object.assign you can extend the blueprint in any way you like for other schema's, without modifying the same object.
You can create a Schema Factory function that accepts a Schema definition and optional schema options, which then merges the passed in Schema definition and options with the Schema fields and options which you want to share across schemas.
Example illustrating this (assuming you want to share or extend a schema that has the fields email and is_verified and the timestamps option enabled):
// schemaFactory.js
const mongoose = require('mongoose');
const SchemaFactory = (schemaDefinition, schemaOptions) => {
return new mongoose.Schema({
{
email: {type: String, required: true},
is_verified: {type: Boolean, default: false},
// spread/merge passed in schema definition
...schemaDefinition
}
}, {
timestamps: true,
// spread/merge passed in schema options
...schemaOptions
})
}
module.exports = SchemaFactory;
The SchemaFactory function can then be called with:
// schemas.js
const SchemaFactory = require("./schemaFactory.js")
const UserSchema = SchemaFactory({
first_name: String,
password: {type: String, required: true}
});
const AdminSchema = SchemaFactory({
role: {type: String, required: true}
}, {
// we can pass in schema options to the Schema Factory
strict: false
});
Now the UserSchema and AdminSchema will contain both the email and is_verified field as well as have the timestamps option enabled, along with the schema fields and options you pass along.
I just published a mongoose-super npm module. Although I did some testing, it is still in an experimental stage. I'm interested to know if it works well for the applications of my fellow SO users!
The module provides a inherit() convenience function that returns a child Mongoose.js model based on a parent model and a child schema extension. It also augments models with a super() method to call parent model methods. I added this functionality because it is something I missed in other extension/inheritance libraries.
The inherit convenience function simply uses the discriminator method.
Some people have in other places suggested using utils.inherits to extend schemas. Another simple way would be to simply set up an object with settings and create Schemas from it, like so:
var settings = {
one: Number
};
new Schema(settings);
settings.two = Number;
new Schema(settings);
It's a bit ugly though, since you're modifying the same object. Also I'd like to be able to extend plugins and methods etc. Thus my preferred method is the following:
function UserSchema (add) {
var schema = new Schema({
someField: String
});
if(add) {
schema.add(add);
}
return schema;
}
var userSchema = UserSchema();
var adminSchema = UserSchema({
anotherField: String
});
Which happens to answer your second question that yes, you can add() fields. So to modify some properties of the Schema, a modified version of the above function would solve your problem:
function UserSchema (add, nameAndPhoneIsRequired) {
var schema = new Schema({
//...
firstname: {type: String, validate: firstnameValidator, required: nameAndPhoneIsRequired},
lastname: {type: String, validate: lastnameValidator, required: nameAndPhoneIsRequired},
phone: {type: String, validate: phoneValidator, required: nameAndPhoneIsRequired},
});
if(add) {
schema.add(add);
}
return schema;
}
You can extend the original Schema#obj:
const AdminSchema = new mongoose.Schema({}, Object.assign(UserSchema.obj, {...}))
Example:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
email: {type: String, unique: true, required: true},
passwordHash: {type: String, required: true},
firstname: {type: String},
lastname: {type: String},
phone: {type: String}
});
// Extend function
const extend = (Schema, obj) => (
new mongoose.Schema(
Object.assign({}, Schema.obj, obj)
)
);
// Usage:
const AdminUserSchema = extend(UserSchema, {
firstname: {type: String, required: true},
lastname: {type: String, required: true},
phone: {type: String, required: true}
});
const User = mongoose.model('users', UserSchema);
const AdminUser = mongoose.model('admins', AdminUserSchema);
const john = new User({
email: 'user@site.com',
passwordHash: 'bla-bla-bla',
firstname: 'John'
});
john.save();
const admin = new AdminUser({
email: 'admin@site.com',
passwordHash: 'bla-bla-bla',
firstname: 'Henry',
lastname: 'Hardcore',
// phone: '+555-5555-55'
});
admin.save();
// Oops! Error 'phone' is required
Or use this npm module with the same approach:
const extendSchema = require('mongoose-extend-schema'); // not 'mongoose-schema-extend'
const UserSchema = new mongoose.Schema({
firstname: {type: String},
lastname: {type: String}
});
const ClientSchema = extendSchema(UserSchema, {
phone: {type: String, required: true}
});
Check the github repo https://github.com/doasync/mongoose-extend-schema
The simplest way for extending mongoose schema
import { model, Schema } from 'mongoose';
const ParentSchema = new Schema({
fromParent: Boolean
});
const ChildSchema = new Schema({
...ParentSchema.obj,
fromChild: Boolean // new properties come up here
});
export const Child = model('Child', ChildSchema);