mongoose
email validation
validate email
mongoose email validation
data validation

Mongoose - validate email syntax

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

Mongoose validates email syntax through the validate option on a schema field. The most common approach is a regex validator defined inline, but you can also use a custom validator function or a third-party library like validator. Mongoose does not include a built-in email validator, so you always need to provide the logic yourself.

javascript
1const userSchema = new mongoose.Schema({
2    email: {
3        type: String,
4        required: true,
5        validate: {
6            validator: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
7            message: (props) => `${props.value} is not a valid email`
8        }
9    }
10});

This article covers regex validation, custom validators, async validators, the validator library approach, and the practical decisions that determine which approach fits your project.

Schema-Level Regex Validation

The simplest and most common pattern is a regex check defined directly in the schema.

javascript
1const mongoose = require('mongoose');
2
3const userSchema = new mongoose.Schema({
4    name: {
5        type: String,
6        required: true,
7        trim: true
8    },
9    email: {
10        type: String,
11        required: true,
12        unique: true,
13        lowercase: true,
14        trim: true,
15        validate: {
16            validator: function (value) {
17                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
18            },
19            message: (props) => `${props.value} is not a valid email address`
20        }
21    },
22    password: {
23        type: String,
24        required: true
25    }
26});
27
28const User = mongoose.model('User', userSchema);

A few things worth noting here.

The lowercase: true option normalizes the email before validation runs. This prevents duplicates caused by case differences like [email protected] vs [email protected].

The trim: true option strips leading and trailing whitespace, which is a common source of validation failures when data comes from form inputs.

The unique: true option creates a MongoDB unique index, but it is not a Mongoose validator. If uniqueness is violated, MongoDB throws a duplicate key error (code 11000), not a Mongoose ValidationError. Handle both.

Understanding the Regex Pattern

The pattern /^[^\s@]+@[^\s@]+\.[^\s@]+$/ is deliberately simple. Here is what each part matches.

ComponentMatchesPurpose
^Start of stringAnchors the match
[^\s@]+One or more characters that are not whitespace or @Local part (before the @)
@Literal @ signRequired separator
[^\s@]+One or more characters that are not whitespace or @Domain name
\.Literal dotSeparates domain from TLD
[^\s@]+One or more characters that are not whitespace or @Top-level domain
$End of stringAnchors the match

This pattern accepts [email protected] and rejects obvious non-emails like @missing.com, no-at-sign, and spaces [email protected]. It intentionally does not try to implement the full RFC 5322 spec because a "perfect" email regex is thousands of characters long and provides marginal practical benefit. In production, the real validation happens when you send a confirmation email.

Custom Validator Functions

For reusable validation logic, extract the validator into a standalone function.

javascript
1const mongoose = require('mongoose');
2
3function isValidEmail(email) {
4    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
5}
6
7const userSchema = new mongoose.Schema({
8    email: {
9        type: String,
10        required: true,
11        unique: true,
12        lowercase: true,
13        validate: [isValidEmail, 'Invalid email format']
14    }
15});

The array syntax [validatorFn, errorMessage] is a shorthand for the object form. Both are equivalent.

You can extend the custom validator with additional business rules.

javascript
1const BLOCKED_DOMAINS = ['tempmail.com', 'throwaway.email', 'mailinator.com'];
2
3function isValidEmail(email) {
4    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
5        return false;
6    }
7    const domain = email.split('@')[1];
8    if (BLOCKED_DOMAINS.includes(domain)) {
9        return false;
10    }
11    return true;
12}

This is a practical pattern for SaaS applications that want to reject disposable email addresses at registration time.

Using the validator Library

The validator npm package provides a battle-tested isEmail function that handles edge cases better than most hand-written regexes.

bash
npm install validator
javascript
1const mongoose = require('mongoose');
2const validator = require('validator');
3
4const userSchema = new mongoose.Schema({
5    email: {
6        type: String,
7        required: true,
8        unique: true,
9        lowercase: true,
10        trim: true,
11        validate: [validator.isEmail, 'Invalid email format']
12    }
13});

validator.isEmail supports options for stricter or more permissive checking.

javascript
1validate: {
2    validator: function (value) {
3        return validator.isEmail(value, {
4            allow_display_name: false,
5            allow_utf8_local_part: false
6        });
7    },
8    message: 'Invalid email format'
9}
ApproachProsCons
Inline regexNo dependency, easy to readLimited coverage of edge cases
Custom functionReusable, can add business rulesStill relies on your own regex
validator.isEmailWell-tested, configurable, maintainedAdds a dependency

For most projects, validator.isEmail is the right choice. The library is small, widely used, and maintained.

Async Validators

Mongoose supports async validators for checks that require database or external service calls. A common use case is checking whether an email already exists before save.

javascript
1const userSchema = new mongoose.Schema({
2    email: {
3        type: String,
4        required: true,
5        lowercase: true,
6        validate: {
7            validator: async function (value) {
8                const existingUser = await mongoose.model('User').findOne({ email: value });
9                // If editing an existing document, allow the same email
10                if (existingUser) {
11                    return this._id.equals(existingUser._id);
12                }
13                return true;
14            },
15            message: 'Email is already registered'
16        }
17    }
18});

Note that the isAsync: true option is deprecated in Mongoose 5+. Mongoose now detects async validators automatically when the function returns a Promise. Simply use async/await or return a Promise.

Handling Validation Errors

When validation fails, Mongoose throws a ValidationError with details about each failing field.

javascript
1const user = new User({ name: 'Test', email: 'not-an-email', password: 'secret' });
2
3try {
4    await user.save();
5} catch (error) {
6    if (error.name === 'ValidationError') {
7        for (const field in error.errors) {
8            console.log(`${field}: ${error.errors[field].message}`);
9        }
10    }
11}

Output.

text
email: not-an-email is not a valid email address

For uniqueness violations, the error comes from MongoDB, not Mongoose validation.

javascript
1try {
2    await user.save();
3} catch (error) {
4    if (error.code === 11000) {
5        console.log('Duplicate email address');
6    }
7}

Handle both error types in your API layer to return consistent responses to clients.

Combining Multiple Validators

A single field can have multiple validators by using an array.

javascript
1const userSchema = new mongoose.Schema({
2    email: {
3        type: String,
4        required: [true, 'Email is required'],
5        unique: true,
6        lowercase: true,
7        validate: [
8            {
9                validator: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
10                message: 'Invalid email syntax'
11            },
12            {
13                validator: (v) => !v.endsWith('.test'),
14                message: 'Test domain emails are not allowed'
15            }
16        ]
17    }
18});

Mongoose runs all validators and collects all failures into the ValidationError, so the client gets a complete list of problems in a single response.

Common Pitfalls

Treating unique: true as a validator. It creates a MongoDB index, not a Mongoose validation rule. It does not produce a ValidationError. It produces a MongoDB duplicate key error with code 11000. Handle both types.

Writing a regex that rejects valid emails. Addresses like [email protected], [email protected], and "quoted local"@example.com are all valid per RFC 5322. Overly strict regexes reject real users. When in doubt, keep the regex simple and validate by sending a confirmation email.

Forgetting lowercase and trim. Without normalization, [email protected] and [email protected] are treated as different values by the unique index, causing duplicates.

Using isAsync: true on Mongoose 5+. This option is deprecated. Return a Promise or use async/await instead.

Not indexing the email field. The unique: true option creates an index, but if you also query by email for login, ensure the index is in place. Without it, every login triggers a collection scan.

Summary

  • Mongoose does not include a built-in email validator. You must provide one through validate.
  • The simplest approach is an inline regex like /^[^\s@]+@[^\s@]+\.[^\s@]+$/.
  • For production applications, the validator library's isEmail function is more robust and well-maintained.
  • Use lowercase: true and trim: true to normalize email values before validation.
  • Handle both ValidationError (from Mongoose) and duplicate key errors (from MongoDB, code 11000) in your error handling.
  • Async validators detect promises automatically in Mongoose 5+. Do not use isAsync: true.

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