LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 3617 min read

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();
});
Never Store Plaintext Passwords

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.

Next Lesson →

Connecting a Node.js App to MongoDB