What is Mongoose, and why use it instead of the MongoDB driver?
Mongoose is an ODM (object document mapper) for Node.js, built on the official MongoDB driver. It adds schemas with types, defaults and validation, model classes with your own methods, middleware hooks, and
populatefor references. Use it when you want one enforced document shape in the application; use the driver directly for full control, less overhead or very dynamic data.What is the difference between a schema, a model and a document in Mongoose?
A schema describes the documents: fields, types, defaults, validators and indexes. A model is a class compiled from a schema and bound to a collection (
Userusesusers); its static methods query that collection. A document is one instance of a model, with methods such assave(). Note thatunique: trueis not a validator: it only builds a unique index.const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true }, age: { type: Number, min: 13 }, }, { timestamps: true }); // adds createdAt and updatedAt const User = mongoose.model('User', userSchema); const user = await User.create({ email: 'Asha@Example.com', age: 21 }); console.log(user.email); // asha@example.comHow does validation work in Mongoose?
Validators run before
save()andcreate(): built-in ones such asrequired,min,max,enum,matchandminLength, plus customvalidatefunctions, which can be async. A failure rejects with aValidationErrorthat lists every invalid path, and nothing is written. Mongoose casts values first, so'21'becomes 21 for a Number field, and a value it cannot cast is reported as a cast error.Do Mongoose validators run on
updateOneandfindOneAndUpdate?Not by default. Validators run on
save(), while query updates go straight to MongoDB, soUser.updateOne({ _id: id }, { age: 5 })stores an invalid age. Pass{ runValidators: true }(or set it globally) to validate the updated paths. Even then only the fields in the update are checked, and inside a custom validatorthisis the query, not the document.What are Mongoose middleware hooks, and what are the common traps?
Functions that run before (
pre) or after (post) an operation. Document middleware (save,validate) gets the document asthis; query middleware (find,updateOne,findOneAndUpdate) gets the query. Traps: an arrow function has nothis, andfindOneAndUpdatedoes not runsavehooks, so password hashing inpre('save')is skipped on that path.userSchema.pre('save', async function () { if (!this.isModified('password')) return; this.password = await bcrypt.hash(this.password, 10); });How does
populatework, and how is it different from$lookup?A field stores an ObjectId with
ref: 'User', andpopulate('author')runs a second query for the referenced documents and puts them in place of the ids, in your application.$lookupjoins on the server in one aggregation.populateis convenient, but it costs an extra query per populated path, and nested populates multiply them; for heavy reports, use$lookupor embed the fields you need.What does
lean()do in Mongoose?It makes a query return plain JavaScript objects instead of Mongoose documents: no change tracking, getters, setters, virtuals, defaults or
save(). That uses much less memory and CPU, so use it for read-only endpoints that just send JSON. Skip it when you will modify the result and save it.const users = await User.find({ active: true }).select('name email').lean();What are virtuals in Mongoose?
Properties computed from other fields, defined on the schema and not stored in MongoDB, such as
fullNamebuilt from first and last names. Because they are not in the database, you cannot query or sort by them, andtoJSON()leaves them out unless you settoJSON: { virtuals: true }.userSchema.virtual('fullName').get(function () { return `${this.first} ${this.last}`; });What is the difference between instance methods and statics in Mongoose?
Instance methods (
schema.methods.name) are called on one document and usethisas that document, such asuser.checkPassword(pw). Statics (schema.statics.name) are called on the model and usually run queries, such asUser.findByEmail(email). Define both beforemongoose.model()compiles the schema, and with regular functions, not arrow functions.Is a Mongoose query a promise?
No, it is a thenable:
await User.find()works because a query has athen()method, and the query runs when awaited..exec()returns a real promise and gives better stack traces. A query object runs only once: Mongoose 6 and later throwQuery was already executedif you await the same object twice. Mongoose 7 removed callback support, so useawait.Should Mongoose build indexes automatically in production?
Usually not. With
autoIndexon (the default), each app start callscreateIndexfor every index in the schemas, so adding an index to a schema starts a build on a large collection under production load. Mongoose's documentation recommends turning it off in production and building indexes in a deployment step, for example withModel.createIndexes().What are discriminators in Mongoose?
Schema inheritance in one collection: a base model (
Event) and child models (ClickEvent,PurchaseEvent) that add their own fields. Mongoose stores the type in a__tfield and filters on it, soClickEvent.find()returns only clicks whileEvent.find()returns every event. Use them when the types share most fields and are queried together.
Mongoose Interview Questions
Mongoose, the Node.js ODM for MongoDB: schemas, models, validation, middleware, populate, lean and the traps around them.