Search code examples
mongodbmongoosemiddleware

Circular Reference Issue in Mongoose pre-hook


In my MongoDB/Node backend environment I am using Mongoose pre and post hook middleware to check what's changed on the document, in order to create some system notes as a result.

One problem I'm running into is that when I try and lookup the record for the document in question I get a "Customer.findOne()" is not a function error. This is ONLY a problem when I'm looking up a record from the same collection from which the model just launched this pre and post hook triggers file. In other words, if my "Customer" model kicks off functions in a pre hook function in an external file, then I get an error if I then try and lookup a Customer with a standard findOne():

My customer model looks something like this:

module.exports = mongoose.model(
  "Customer",
  mongoose
    .Schema(
      {
        __v: {
          type: Number,
          select: false
        },
        deleted: {
          type: Boolean,
          default: false
        },
        // Other props
        searchResults: [
          {
            matchKey: String,
            matchValue: String
          }
        ]
      },
      {
        timestamps: true
      }
    )
    .pre("save", function(next) {
      const doc = this;
      trigger.preSave(doc);
      next();
    })
    .post("save", function(doc) {
      trigger.postSave(doc);
    })
    .post("update", function(doc) {
      trigger.postSave(doc);
    })
    .post("findOneAndUpdate", function(doc) {
      trigger.postSave(doc);
    })
);

... the problematic findOne() function in the triggers file being called from the model looks like this:

const Customer = require("../../models/customer");

exports.preSave = async function(doc) {
   this.preSaveDoc = await Customer.findOne({
     _id: doc._id
   }).exec();
};

To clarify, this is NOT a problem if I'm using a findOne() to lookup a record from a different collection in this same triggers file. Then it works fine. See below when finding a Contact -- no problem here:

const Contact = require("../../models/contact");

exports.preSave = async function(doc) {
   this.preSaveDoc = await Contact.findOne({
     _id: doc._id
   }).exec();
};

The workaround I've found is to use Mongo instead of Mongoose, like so:

exports.preSave = async function(doc) {
  let MongoClient = await require("../../config/database")();
  let db = MongoClient.connection.db;

  db.collection("customers")
    .findOne({ _id: doc._id })
    .then(doc => {
      this.preSaveDoc = doc;
    });
}

... but I'd prefer to use Mongoose syntax here. How can I use a findOne() in a pre-hook function being called from the same model/collection as the lookup type?


Solution

  • I have ran similar issue few days ago. Effectively it is a circular dependency problem. When you call .findOne() on your customer model it doesn't exist as it is not exported yet. You should probably try something like that :

    const customerSchema = mongoose.Schema(...);
    
    customerSchema.pre("save", async function(next) {
      const customer = await Customer.findOne({
        _id: this._id
      }).exec();
      trigger.setPreSaveDoc(customer);
      next();
    })
    
    const Customer = mongoose.model("Customer", customerSchema)
    
    module.export Customer;
    

    Here customer will be defined because it is not called (the pre hook) before its creation.

    As an easier way (I am not sure about it) but you could try to move the Contact import in your Trigger file under the save function export. That way I think the decencies may works.

    Did it helps ?