Mongoose: extending schemas

44,881

Solution 1

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;
}

Solution 2

Mongoose 3.8.1 now has support for Discriminators. A sample, from here: http://mongoosejs.com/docs/api.html#model_Model.discriminator

function BaseSchema() {
  Schema.apply(this, arguments);

  this.add({
    name: String,
    createdAt: Date
  });
}
util.inherits(BaseSchema, Schema);

var PersonSchema = new BaseSchema();
var BossSchema = new BaseSchema({ department: String });

var Person = mongoose.model('Person', PersonSchema);
var Boss = Person.discriminator('Boss', BossSchema);

Solution 3

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);

Solution 4

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: '[email protected]',
  passwordHash: 'bla-bla-bla',
  firstname: 'John'
});

john.save();

const admin = new AdminUser({
  email: '[email protected]',
  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

Solution 5

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.

Share:
44,881
Sukhpreet Singh Alang
Author by

Sukhpreet Singh Alang

Updated on July 09, 2022

Comments

  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang almost 2 years

    Currently I have two almost identical schemas:

    var userSchema = mongoose.Schema({
    
        email: {type: String, unique: true, required: true, validate: emailValidator},
        passwordHash: {type: String, required: true},
    
        firstname: {type: String, validate: firstnameValidator},
        lastname: {type: String, validate: lastnameValidator},
        phone: {type: String, validate: phoneValidator},
    
    });
    

    And

    var adminSchema = mongoose.Schema({
    
        email: {type: String, unique: true, required: true, validate: emailValidator},
        passwordHash: {type: String, required: true},
    
        firstname: {type: String, validate: firstnameValidator, required: true},
        lastname: {type: String, validate: lastnameValidator, required: true},
        phone: {type: String, validate: phoneValidator, required: true},
    
    });
    

    Their only difference is in validation: Users do not need a firstname, lastname or phone. Admins however must have these properties defined.

    Unfortunately the above code is not very DRY, as they're almost identical. Therefore I am wondering if it is possible to build an adminSchema based on the userSchema. E.g.:

    var adminSchema = mongoose.Schema(userSchema);
    adminSchema.change('firstname', {required: true});
    adminSchema.change('lastname', {required: true});
    adminSchema.change('phone', {required: true});
    

    Obviously that's just pseudocode. Is something like this possible?

    Another very similar question is if it is possible to create a new schema based on another, and add some more properties to it. For example:

    var adminSchema = mongoose.Schema(userSchema);
        adminSchema.add(adminPower: Number);