Mongoose
01 / 02

Middleware, Indexes & Transactions

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