Mongoose
validation errors
error handling
JavaScript
Node.js

Handling Mongoose validation errors – where and how?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Mongoose validation errors should usually be handled at the point where you save or update data, then translated into a consistent application-level response. The goal is not just to catch the error, but to return useful field-level messages while keeping route handlers and service code predictable.

What a Mongoose Validation Error Looks Like

Mongoose runs schema validation before certain write operations. When validation fails, it throws an error whose name is usually ValidationError, and the individual field failures appear under error.errors.

javascript
1const mongoose = require("mongoose");
2
3const userSchema = new mongoose.Schema({
4  name: {
5    type: String,
6    required: [true, "Name is required"],
7    minlength: [3, "Name must be at least 3 characters long"],
8  },
9  email: {
10    type: String,
11    required: [true, "Email is required"],
12    match: [/^\S+@\S+\.\S+$/, "Email format is invalid"],
13  },
14  age: {
15    type: Number,
16    min: [18, "Age must be at least 18"],
17  },
18});
19
20const User = mongoose.model("User", userSchema);

If code tries to save an invalid document, the thrown error contains one entry per invalid field. That structure is exactly what you want to convert into an API-friendly response.

Handle It Near the Write Operation

The most natural place to catch validation errors is where the database write occurs. That might be a service layer, a controller, or a repository function depending on the project structure.

javascript
1async function createUser(data) {
2  try {
3    const user = new User(data);
4    await user.save();
5    return { ok: true, user };
6  } catch (error) {
7    if (error instanceof mongoose.Error.ValidationError) {
8      return {
9        ok: false,
10        type: "validation",
11        errors: formatValidationErrors(error),
12      };
13    }
14
15    throw error;
16  }
17}

This keeps Mongoose-specific knowledge close to the persistence code while still returning a clean result shape to the rest of the application.

Format Errors into a Consistent Shape

Do not send the raw Mongoose error object directly to clients. It contains more detail than the client needs and ties your API format to one library.

javascript
1function formatValidationErrors(error) {
2  const details = {};
3
4  for (const [field, fieldError] of Object.entries(error.errors)) {
5    details[field] = {
6      message: fieldError.message,
7      kind: fieldError.kind,
8      value: fieldError.value,
9    };
10  }
11
12  return details;
13}

An Express route can then translate that into a 400 or 422 response:

javascript
1app.post("/users", async (req, res, next) => {
2  try {
3    const result = await createUser(req.body);
4
5    if (!result.ok && result.type === "validation") {
6      return res.status(422).json({ errors: result.errors });
7    }
8
9    res.status(201).json(result.user);
10  } catch (error) {
11    next(error);
12  }
13});

This approach keeps the HTTP layer simple and makes validation output consistent across routes.

Validate Updates Carefully

One important Mongoose detail is that update operations do not always validate the same way save() does. If you use methods such as updateOne, findOneAndUpdate, or updateMany, you often need runValidators: true.

javascript
1await User.findByIdAndUpdate(
2  userId,
3  { age: 15 },
4  {
5    new: true,
6    runValidators: true,
7  }
8);

Without runValidators: true, the update may bypass schema validation and write invalid data. This is one of the most common sources of confusion when developers say "Mongoose validation is not running."

validateSync for Early Checks

Sometimes you want validation results before hitting the database, for example in tests or in a service that builds documents in memory first. In that case, validateSync() can be useful.

javascript
1const user = new User({ name: "Al", email: "bad-email", age: 12 });
2const error = user.validateSync();
3
4if (error) {
5  console.log(formatValidationErrors(error));
6}

This does not replace save-time validation, but it is helpful when you want early feedback or deterministic unit tests.

Common Pitfalls

  • Catching every database error the same way instead of separating validation failures from unexpected system errors.
  • Returning the raw Mongoose error object to clients instead of a stable response format.
  • Forgetting runValidators: true on update operations.
  • Handling validation only in routes and duplicating the same logic across endpoints.
  • Assuming database uniqueness errors are validation errors when they often come back as different MongoDB or driver errors.

Summary

  • Handle Mongoose validation errors where writes happen, then translate them into a consistent response.
  • Detect validation failures with mongoose.Error.ValidationError.
  • Extract field messages from error.errors instead of sending the raw error object.
  • Use runValidators: true for update operations that should obey schema rules.
  • Use validateSync() when you need early in-memory validation before saving.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.