Skip to content

Mongoose

Hooks, Virtuals and Indexes

Middleware, computed fields, and declaring indexes next to the schema.

IntermediateAbout 6 minutes

Hooks (pre / post) run around save, deleteOne, some queries. Virtuals are computed in Node and not stored unless you enable that. Indexes declared on the schema are how Mongoose creates { email: 1 } unique — they still cost writes like any index.

userSchema.pre("save", function (next) {
  if (this.isModified("email")) this.email = this.email.toLowerCase();
  next();
});

userSchema.virtual("label").get(function () {
  return `${this.name} · ${this.city}`;
});

userSchema.index({ city: 1, createdAt: -1 });
pre('save') sees this. Query updates do not run save hooks.

updateOne does not run pre('save'). Hashing a password only in save and then User.updateOne({ $set: { password } }) stores plaintext. pre('findOneAndUpdate') exists; it is easy to forget. Virtuals need .toJSON({ virtuals: true }) or they vanish in res.json. __v is Mongoose's version key — optimistic concurrency only if every write goes through Mongoose and increments it; driver updates skip __v unless you $inc it.

Interview question

Do Mongoose middleware hooks run on updateOne?

Think about it first.