BugForge
All lessons
Databases · 01/09 8 min

Mongoose & MongoDB essentials

Queries, ObjectIds, CastError, {new: true}, populate, unique indexes, hooks.

Queries you'll see every time

Call Returns Gotcha
Model.find(filter) array (maybe empty) never null, so if (!docs) is wrong
Model.findOne(filter) doc or null check for null → 404
Model.findById(id) doc or null invalid id string → CastError
findByIdAndUpdate(id, upd) the old doc pass { new: true } for the updated one
Model.create(data) new doc runs validators and pre('save') hooks
updateOne / updateMany { matchedCount, modifiedCount } skips save hooks

ObjectIds

doc._id is an ObjectId object, not a string. doc.owner === req.user.id is always false; use doc.owner.equals(id) or String(doc.owner) === id.

Errors to translate (not 500!)

  • CastError (bad id) → 400
  • ValidationError (required/min/enum) → 400 with the field messages
  • E11000 duplicate key (unique index) → 409 Conflict
  • null result → 404

Hooks and this

// ❌ arrow function: `this` is not the document
userSchema.pre("save", async () => { this.password = await hash(this.password); });
// ✅ and only re-hash when it changed
userSchema.pre("save", async function () {
  if (this.isModified("password")) this.password = await bcrypt.hash(this.password, 10);
});

Atomic updates

Read-modify-write in JS (doc.stock -= 1; await doc.save()) races under concurrency. Push the condition into the query:

await Product.updateOne({ _id, stock: { $gte: qty } }, { $inc: { stock: -qty } });

populate

Order.find().populate("user", "name email") swaps the stored ObjectId for the referenced document. Without it, order.user.name is undefined.

Next in Databases
Your first database with Node