question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Inject model into schema for post hook

See original GitHub issue

Let’s say we’re having two schemas called conversation and message.

MessageSchema:

export const MessageSchema = new mongoose.Schema({
	

	Created: { type: Date, default: Date.now },

	ConversationID: {
		type: mongoose.Schema.Types.ObjectId,
		ref: 'conversation'
	},

	SenderID: mongoose.Schema.Types.ObjectId,

	Text: String, // Message Text oder Filename
}

ConversationSchema:

export const ConversationSchema = new mongoose.Schema({
	Title: String,
	Description: String,

	Participants: [{
		type: mongoose.Schema.Types.ObjectId // userIDs
	}],

	Created: {
		type: Date,
		default: Date.now
	}
});

Whenever a conversation is deleted I want mongoose to automatically delete all related messages. Mongoose has a functionality called Middleware for that (also called pre and post hooks).

Docs: http://mongoosejs.com/docs/middleware.html

The problem is, that I’ll need the MessageModel instance for the remove query.

ConversationSchema.post('findOneAndRemove', function (err, doc, next) {
	// Remove messages related to this conversation

	Message.remove({
		ConversationID: doc._id
	}).then(() => {
		next();
	});
});

How can I retrieve the model instance? As far as I understand there is no nestjs injection available for schemas.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:4
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

21reactions
elielliscommented, Feb 27, 2020

I just want to leave a comment here for anyone who comes across this in the future. For my purposes, I was interested in using multiple models in the schema definitions of each other within pre/post hooks, e.g. I want delete all user posts when a user gets deleted, etc. I found that registering hooks with asynchronous behavior wasn’t working with the .forFeature(), did some digging in the documentation, tried forFeatureAsync(), found this issue, and was able to piece together a solution.

You can use .forFeatureAsync() to provide your models, as outlined in the documentation, and inject the other modules into the factory functions you define in the exact same way you would in a service. The only difference being that you do not decorate any schema factory method parameters with@InjectModel, but instead add an entry in the inject array of the forFeatureAsync where the entry is defined by using the getModelToken() method from mongoose.util.ts mentioned in the testing portion of the documentation.

Here is a short example:

// models/index.ts
import { AsyncModelFactory, getModelToken } from '@nestjs/mongoose';
import { UserSchemaFactory } from './user/user';
import { PostSchemaFactory } from './posts/post';

const UserModelToken = 'User';
const PostModelToken = 'Post';

export const MONGOOSE_MODELS: AsyncModelFactory[] = [
  { name: PostModelToken, useFactory: PostSchemaFactory }, // must also define a model with the same name (PostModelToken)
  {
    name: UserModelToken,
    useFactory: UserSchemaFactory,
    inject: [getModelToken(PostModelToken)],
  },
]

// user.schema.ts
const UserSchema = new Schema({ ... });
export const UserSchemaFactory = (
  postModel: Model<UserPost>, // postModel will be injected
): Schema<any> => {
  // remove all user posts when a user is deleted
  UserSchema.pre<User>('remove', async function() {
    await postModel.deleteMany({ author: this._id });
  });

  return UserSchema;
};
5reactions
pozniakascommented, Jul 9, 2021

I created a solution to have Mongoose middleware with keeping OOP principles, but I believe that the functions of decorators could be improved. At least, the final view looks great.

import { Injectable } from '@nestjs/common';
import { MongoMiddleware, MongoSubscriber } from '#MongoDB';

...

@Injectable()
@MongoSubscriber(BeautifulMessageModel)
export class BeautifulMessageSubscriber {
  constructor(private beautifulService: BeautifulService) {}

  @MongoMiddleware('post', 'save')
  async informAboutNewMessage(message: BeautifulMessage) {
    this.beautifulService.letsDoSomethingFun(message);
  }

  @MongoMiddleware('pre', 'save')
  async informAboutNewMessage(message: BeautifulMessage) {
    this.beautifulService.letsDoSomethingSad(message);
  }
}

// Make this middleware to work with specifying this class as a provider:
//  providers: [BeautifulMessageSubscriber],
import type { Schema } from 'mongoose';

type Model = {
  name: string;
  schema: Schema;
};

export function MongoSubscriber({ schema }: Model) {
  return function _MongoSubscriber<T extends { new (...args: any[]): {} }>(constr: T) {
    return class extends constr {
      schema: Schema;
      constructor(...args: any[]) {
        super(...args);
        this.schema = schema;
        const methods = Object.getOwnPropertyNames(constr.prototype).filter((v) => v !== 'constructor');
        methods.forEach((method) => this[method]());
      }
    };
  };
}
import type { MongooseDocumentMiddleware } from 'mongoose';

export function MongoMiddleware(type: 'pre' | 'post', middleware: MongooseDocumentMiddleware) {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const method = originalMethod.bind(this);
      this.schema[type](middleware, async function (document, next) {
        if (type === 'pre') {
          await method(this);
          return document();
        }
        await method(document);
        next();
      });
    };
  };
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

pre-hook & post-hook
A SQL statement (or list of SQL statements) to be run before or after a model, seed, or snapshot is built. Pre- and...
Read more >
Mongoose v6.8.1: Middleware
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. Middleware is specified on ......
Read more >
Mongoose pre and post hooks do not work on models ...
I am using mongoose 6.1.5. I have a collection containing multiple types of related documents. Here is parameter-descriptor-item.model.js
Read more >
@pre & @post | typegoose
@pre is used to set Document & Query pre hooks, works like schema.pre only difference is the switched options and method ( fn...
Read more >
Mongoose | Ts.ED - A Node.js and TypeScript Framework ...
Add a plugin, PreHook method and PostHook on your model; Inject a Model to a Service, Controller, Middleware, etc. Create and manage multiple ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found