Home > Enterprise >  NestJS Mongoose Schema Inheritence
NestJS Mongoose Schema Inheritence

Time:09-28

I am attempting to inherit Mongoose Schemas or SchemaDefitions within NestJS but I am not having much luck. I am doing this so I can share Base and Common Schema Definition Details such as a virtual('id') and a nonce, we have attached to each of the entities. Each schema definition should have its own collection in Mongo, so discriminators will not work.

I tried to implement this in the following different ways

First, I have the following Base Schema Definition defined:

base.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { TimeStamps } from './timestamps.schema';

export type BaseDocument = BaseSchemaDefinition & Document;

@Schema({
  toJSON: {
    virtuals: true,
    transform: function (doc: any, ret: any) {
      delete ret._id;
      delete ret.__v;
      return ret;
    },
  },
})
export class BaseSchemaDefinition {
  @Prop({
    type: Types.ObjectId,
    required: true,
    default: Types.ObjectId,
  })
  nonce: Types.ObjectId;

  @Prop()
  timestamps: TimeStamps;
}

I then inherit the schema definition and create the schema so it can be used later in my services and controllers by the following:

person.schema.ts

import { Prop, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Document } from 'mongoose';
import { Address } from './address.schema';
import { BaseSchemaDefinition } from './base.schema';

export type PersonDocument = PersonSchemaDefintion & Document;

export class PersonSchemaDefintion extends BaseSchemaDefinition {
  @Prop({ required: true })
  first_name: string;

  @Prop({ required: true })
  last_name: string;

  @Prop()
  middle_name: string;

  @Prop()
  data_of_birth: Date;

  @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Address' }] })
  addresses: [Address];
}

const PersonSchema = SchemaFactory.createForClass(PersonSchemaDefintion);

PersonSchema.virtual('id').get(function (this: PersonDocument) {
  return this._id;
});

export { PersonSchema };

This results in only allowing me to create and get properties defined in the BaseSchemaDefinition.

{ "timestamps": { "deleted": null, "updated": "2021-09-21T16:55:17.094Z", "created": "2021-09-21T16:55:17.094Z" }, "_id": "614a0e75eb6cb52aa0ccd026", "nonce": "614a0e75eb6cb52aa0ccd028", "__v": 0 }

Second, I then tried to implement inheritance by using the method described here Inheriting Mongoose schemas (different MongoDB collections)

base.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { TimeStamps } from './timestamps.schema';

export type BaseDocument = BaseSchemaDefinition & Document;

@Schema({
  toJSON: {
    virtuals: true,
    transform: function (doc: any, ret: any) {
      delete ret._id;
      delete ret.__v;
      return ret;
    },
  },
})
export class BaseSchemaDefinition {
  @Prop({
    type: Types.ObjectId,
    required: true,
    default: Types.ObjectId,
  })
  nonce: Types.ObjectId;

  @Prop()
  timestamps: TimeStamps;
}

const BaseSchema = SchemaFactory.createForClass(BaseSchemaDefinition);

BaseSchema.virtual('id').get(function (this: BaseDocument) {
  return this._id;
});

export { BaseSchema };

person.schema.ts

import { Prop } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Document } from 'mongoose';
import { Address } from './address.schema';
import { BaseSchema, BaseSchemaDefinition } from './base.schema';

export type PersonDocument = PersonSchemaDefintion & Document;

export class PersonSchemaDefintion extends BaseSchemaDefinition {
  @Prop({ required: true })
  first_name: string;

  @Prop({ required: true })
  last_name: string;

  @Prop()
  middle_name: string;

  @Prop()
  data_of_birth: Date;

  @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Address' }] })
  addresses: [Address];
}

export const PersonSchema = Object.assign(
  {},
  BaseSchema.obj,
  PersonSchemaDefintion,
);

Results in the same output. Not sure why the inheritance is not taking

The following is the service code that uses the schemas and builds the models

person.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import {
  PersonSchemaDefintion,
  PersonDocument,
} from 'src/schemas/person.schema';
import { TimeStamps } from 'src/schemas/timestamps.schema';

@Injectable()
export class PersonService {
  constructor(
    @InjectModel(PersonSchemaDefintion.name)
    private personModel: Model<PersonDocument>,
  ) {}

  async create(
    personModel: PersonSchemaDefintion,
  ): Promise<PersonSchemaDefintion> {
    personModel.timestamps = new TimeStamps();
    const createdPerson = new this.personModel(personModel);

    return createdPerson.save();
  }

  async update(
    id: string,
    changes: Partial<PersonSchemaDefintion>,
  ): Promise<PersonSchemaDefintion> {
    const existingPerson = this.personModel
      .findByIdAndUpdate(id, changes)
      .exec()
      .then(() => {
        return this.personModel.findById(id);
      });
    if (!existingPerson) {
      throw Error('Id does not exist');
    }
    return existingPerson;
  }

  async findAll(): Promise<PersonSchemaDefintion[]> {
    return this.personModel.find().exec();
  }

  async findOne(id: string): Promise<PersonSchemaDefintion> {
    return this.personModel.findById(id).exec();
  }

  async delete(id: string): Promise<string> {
    return this.personModel.deleteOne({ _id: id }).then(() => {
      return Promise.resolve(`${id} has been deleted`);
    });
  }
}

I can provide additional details if it is needed

CodePudding user response:

After fiddling around with it for a while I found the right combination that appears to work when leveraging these technologies

Here is the base class

base.schema.ts

import { Prop, Schema } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { TimeStamps } from './timestamps.schema';

export type BaseDocument = Base & Document;

@Schema()
export class Base {
  @Prop({
    type: Types.ObjectId,
    required: true,
    default: Types.ObjectId,
  })
  nonce: Types.ObjectId;

  @Prop()
  timestamps: TimeStamps;
}

Here is the class that inherits the base.schema

person.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { Address } from './address.schema';
import { Base } from './base.schema';

export type PersonDocument = Person & Document;

@Schema({
  toJSON: {
    virtuals: true,
    transform: function (doc: any, ret: any) {
      delete ret._id;
      delete ret.__v;
      return ret;
    },
  },
})
export class Person extends Base {
  @Prop({ required: true })
  first_name: string;

  @Prop({ required: true })
  last_name: string;

  @Prop()
  middle_name: string;

  @Prop()
  data_of_birth: Date;

  @Prop({ type: [{ type: Types.ObjectId, ref: 'Address' }] })
  addresses: [Address];
}
const PersonSchema = SchemaFactory.createForClass(Person);

PersonSchema.virtual('id').get(function (this: PersonDocument) {
  return this._id;
});

export { PersonSchema };

The only thing I would like to improve on is moving the virtual('id') to the base class. However the schema inheritance does not work. At this point, it will only work with the Schema Definition. This at least gets me in the right direction. If anyone has a way to improve on this please contribute.

  • Related