Middleware, Indexes & Transactions
Middleware (Hooks)
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next(); // MUST be called (or return the resolved Promise) — forgetting
}); // this makes the save hang indefinitely with no clear error
// Or the Promise-returning style — no `next` parameter needed
userSchema.pre('save', async function () {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
});Compound Indexes
// Speeds up a query filtering by author AND sorting by date together,
// beyond what separate single-field indexes on each would achieve
postSchema.index({ author: 1, createdAt: -1 });
await Post.find({ author: authorId }).sort({ createdAt: -1 });Multi-Document Transactions
// Requires a replica set (or sharded cluster) — not standalone MongoDB
const session = await mongoose.startSession();
try {
await session.withTransaction(async () => {
await Account.updateOne(
{ _id: fromId }, { $inc: { balance: -amount } }, { session }
);
await Account.updateOne(
{ _id: toId }, { $inc: { balance: amount } }, { session }
);
});
// both updates commit together, or both roll back on any failure
} finally {
session.endSession();
}Common Default-Value Bug
// WRONG — Date.now() is called ONCE, at schema-definition time. Every
// document ever created from this schema gets the SAME frozen timestamp.
createdAt: { type: Date, default: Date.now() }
// CORRECT — pass the function reference; Mongoose calls it fresh per document
createdAt: { type: Date, default: Date.now }Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free