Mongoose Validation & Middleware
Add built-in and custom validators to a schema, and hook into the document lifecycle with pre/post middleware.
Built-in Validators
Mongoose ships several common validators directly in the schema type definition.
const productSchema = new mongoose.Schema({ name: { type: String, required: true, minlength: 2, maxlength: 100 }, price: { type: Number, required: true, min: 0 }, category: { type: String, enum: ['electronics', 'clothing', 'books'] },});Custom Validators
For anything beyond the built-ins, a validate function runs custom logic against the field's value.
const userSchema = new mongoose.Schema({ email: { type: String, required: true, validate: { validator: (value) => /^.+@.+\..+$/.test(value), message: (props) => `${props.value} is not a valid email address`, }, },});Handling Validation Errors
Attempting to save a document that fails validation throws a ValidationError, containing details on exactly which fields failed and why.
try { await User.create({ email: 'not-an-email' });} catch (err) { if (err.name === 'ValidationError') { console.log(err.errors.email.message); }}Middleware: pre and post Hooks
Mongoose middleware (sometimes called "hooks") runs custom logic before or after a document operation like save, validate, or remove.
userSchema.pre('save', function (next) { console.log('About to save a user:', this.email); next();});
userSchema.post('save', function (doc) { console.log('Saved user with id:', doc._id);});A Practical Example: Hashing a Password
A pre-save hook is the standard place to hash a plaintext password before it's ever written to the database.
import bcrypt from 'bcrypt';
userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); // skip if password wasn't changed this.password = await bcrypt.hash(this.password, 10); next();});This pre-save hook pattern is exactly how a real application ensures a password is always hashed before it touches the database, regardless of where in the code User.create() or user.save() is called from.
FAQs
Not by default for document middleware like the pre('save') example — those specifically wrap Document.prototype.save(); query-level middleware (pre('findOneAndUpdate')) exists separately for that case.
Yes — Mongoose runs all registered pre hooks for a given operation, in the order they were defined.
Summary
Validators enforce data correctness, and pre/post middleware hooks let you run logic — like password hashing — automatically as part of the document lifecycle. Next, you'll connect a complete Node.js/Express app to MongoDB using everything covered so far.