Overview
Validating data before it is saved to a database is a critical aspect of web development. Mongoose, a Node.js object data modeling (ODM) library for MongoDB, includes built-in validation mechanisms that enhance the robustness and reliability of your application by ensuring only valid data is stored. This article aims to guide you through a comprehensive understanding of Mongoose data validation using various code examples demonstrating how to implement validation at different complexity levels. From simple mandatory fields to custom validator functions and asynchronous validation, we’ll explore it all.
Basic Validation
Let’s start by exploring the simplest form of validation in Mongoose which is declaring a field as required.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
username: { type: String, required: true },
email: { type: String, required: true, unique: true }
});
const User = mongoose.model('User', userSchema);
In the snippet above, the username
and email
fields are marked as required, and the email
is also specified to be unique so duplicate entries are prevented.
Type Validation
In Mongoose, each schema path can define its type and optional constraints like minimum or maximum values for numbers.
const productSchema = new Schema({
price: { type: Number, required: true, min: 0 },
stock: { type: Number, default: 0, min: 0 }
});
const Product = mongoose.model('Product', productSchema);
The price
attribute must be a positive number, including zero, as the min
validator enforces it.
String Validation
Mongoose provides validators specifically for string data types like enum
, match
, minlength
, and maxlength
.
const bookSchema = new Schema({
title: { type: String, required: true, trim: true },
status: { type: String, enum: ['AVAILABLE', 'OUT_OF_STOCK', 'DISCONTINUED'], required: true },
isbn: { type: String, match: /[0-9-]{10,13}/ }
});
const Book = mongoose.model('Book', bookSchema);
Above, books can only have a status that is part of a predefined set of values. Also, the isbn
field will only accept a string that matches the specified regular expression pattern for ISBNs.
Custom Validation
For more complex validation rules, you can also define custom validator functions. They allow you to specify your own logic for data validation.
const userSchema = new Schema({
website: {
type: String,
validate: {
validator: function(v) {
return /^(https?:\/\/)?(www\.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/.)?$/.test(v);
},
message: props => `${props.value} is not a valid URL!`
}
}
});
const User = mongoose.model('User', userSchema);
In this schema, a custom validator ensures the website field must be a valid URL format and provides a custom error message if the validation fails.
Asynchronous Validators
In scenarios where you need to make a database call or an asynchronous request during validation, Mongoose supports asynchronous custom validators.
const uniqueEmailValidator = async (email) => {
const existingUser = await User.findOne({ email });
return !existingUser;
};
const userSchema = new Schema({
email: {
type: String,
validate: [uniqueEmailValidator, 'That email address is already taken.'],
lowercase: true,
trim: true,
}
});
const User = mongoose.model('User', userSchema);
Here, the validator function checks whether an email address is already in use by querying the database and validating the result asynchronously.
Conclusion
Mongoose data validation is a powerful feature that can greatly reduce errors and data corruption within your application. By using built-in validators for commons checks and custom validators for more complex scenarios, you can build robust persistence layers with comprehensive data rules. The provided examples range from basic mandatory field requirements to advanced, asynchronous and custom validators, offering a toolbox for ensuring the integrity of your application’s data. Validation best practices include thoughtful schema design and combining server-side validation logic with client-side validation.