Mongoose
Hooks, Virtuals and Indexes
Middleware, computed fields, and declaring indexes next to the schema.
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 });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?
pre('save') / post('save') run on save() and create(), not on updateOne. Query middleware is a different hook (findOneAndUpdate). That is why hashing password only in save is a bug if some paths updateOne. Indexes on the schema are created on the collection; they have the same write cost as createIndex.