Why Mongoose

What Mongoose Adds on Top of the Driver

Take one realistic operation — accept a review posted as JSON, store it, reject bad input — and write it twice. With the driver you own every conversion and check; with Mongoose 243,355 the rules live in the schema and are enforced on every write path.

The same operation with the driver and with MongooseShell
export async function addReview(body) {                       // driver
  if (!ObjectId.isValid(body.bookId)) throw new Error('bad bookId');
  const rating = Number(body.rating);
  if (!Number.isInteger(rating) || rating < 1 || rating > 5) throw new Error('bad rating');
  const doc = { bookId: new ObjectId(body.bookId), userId: body.userId, rating,
                body: String(body.body ?? '').slice(0, 2000), createdAt: new Date() };
  return { ...doc, _id: (await reviews.insertOne(doc)).insertedId };
}
const reviewSchema = new mongoose.Schema({                    // Mongoose
  bookId: { type: Schema.Types.ObjectId, ref: 'Book', required: true },
  rating: { type: Number, required: true, min: 1, max: 5 },
  body:   { type: String, maxlength: 2000 }
}, { timestamps: true });
export const Review = mongoose.model('Review', reviewSchema);
export const addReview = (body) => Review.create(body);   // casts, validates, inserts

The string "3" becomes the number 3, a hex string becomes an ObjectId, createdAt appears on its own, and anything the schema does not mention is dropped rather than stored. The same schema drives updateOne with runValidators: true, insertMany, and the types inferred by InferSchemaType. On top of the comparison in Driver Versus ODM, Mongoose adds pre and post hooks, virtuals and populate.

Be honest about the cost. import 'mongoose' took 350 ms here against 180 ms for the raw driver, which matters in a Lambda cold start and nowhere else, and hydration costs CPU and memory (Queries and the Query Builder). Query middleware makes a findOneAndUpdate do things invisible at the call site. And a schema can lull you into thinking the database enforces it: only an index or a server-side $jsonSchema validator stops a second service from writing whatever it likes. For bulk loads, migrations and change-stream consumers, drop to Model.collection.