mongoose.model('Book', bookSchema) compiles the schema into a class bound to a connection and to the collection books, the name lowercased and pluralized. Instances are documents: data, the schema's behavior and a change list.
const Book = mongoose.model('Book', bookSchema);
const b = new Book({ title: ' Foundation ', authorId: '650000000000000000000001',
year: '1951', tags: ['classic'], rogue: 'ignored' });
console.log('isNew', b.isNew, '| year', typeof b.year, b.year, '| genre', b.genre);
console.log('cast authorId', b.authorId instanceof mongoose.Types.ObjectId,
'| rogue', b.rogue, '| _id length', b._id.toString().length);
await b.save();
b.year = 1952;
console.log('title', JSON.stringify(b.title), '| modified', b.modifiedPaths(),
'| __v', b.__v);isNew true | year number 1951 | genre sci-fi cast authorId true | rogue undefined | _id length 24 title "Foundation" | modified [ 'year' ] | __v 0
Five things happened unasked. '1951' was cast to a number and the hex string to an ObjectId; rogue was dropped by strict; genre took its default; trim: true cleaned the title during validation, which runs inside save(); and timestamps filled in createdAt. The _id was generated in the client process before any network call, so you can build a graph of documents and references, then insert them in any order.
Saving the edit is the interesting part. modifiedPaths() is ['year'], so Mongoose 243,355 sends { $set: { year: 1952 } }, not the whole document: a save is a targeted update, which is why two processes editing different fields do not clobber each other. __v stays at 0 because the version key only increments when an array operation could shift positions — b.tags.push('sf') sends { $push: { tags: { $each: ['sf'] } }, $inc: { __v: 1 } }.
The write paths differ in what they skip, and that is the most common source of "my hook did not run" and "my validator did not fire". doc.save() and Model.create() cast, validate and run save hooks; insertMany casts and validates but runs no save hook; updateOne, updateMany and findOneAndUpdate validate only with runValidators: true and never run a document hook. The query paths are fast and atomic, and they bypass everything a document knows about itself. Middleware and Hooks puts the logic back.