A schema is a map from paths to SchemaTypes plus a bag of options. It is not a model and touches no database: it is a description you can build, extend and share.
const bookSchema = new Schema({
title: { type: String, required: true, trim: true, maxlength: 200 },
authorId: { type: Schema.Types.ObjectId, ref: 'Author', required: true, index: true },
year: { type: Number, min: 1450, max: 2100 },
genre: { type: String, enum: ['sci-fi', 'satire', 'history'], default: 'sci-fi' },
tags: [String], // shorthand for { type: [String] }
price: { type: Schema.Types.Decimal128 }
}, { timestamps: true, strict: true, versionKey: '__v' });The SchemaTypes are String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array, Decimal128, Double, Int32, BigInt, Map, UUID and Union. Each owns a caster and a set of validators: String brings trim, lowercase, match, minlength, maxlength and enum; Number and Date bring min and max. Mixed brings nothing — it is an escape hatch that turns casting and change tracking off, so a Mixed path needs doc.markModified('path') before save() notices an edit.
Of the options, timestamps (off by default) maintains createdAt and updatedAt, versionKey names the optimistic-concurrency counter and false removes it, minimize (on) strips empty objects before writing, and collection overrides the pluralized name. strict is the one that surprises people: its default of true fails silently, so an extra key in a request body is neither stored nor reported. strict: 'throw' raises a StrictModeError naming the field, which is usually what an API wants — although the Bookshelf API already rejects unknown keys at the edge with a .strict() Zod 44,027 schema (Schema Validation with Zod). Validating twice in two vocabularies is a decision, not an oversight: decide which layer owns the message the client sees.
Nesting works two ways. editions: [editionSchema] makes an array of subdocuments, each with its own _id (suppress it with { _id: false }), validation and hooks; meta: { pages: Number } inline creates only the dotted path meta.pages. Finish the schema before the model, because mongoose.model() compiles it and later changes are ignored.