The Problem
You love Mongoose for all it's convenience methods and
valiate-before-saving logic, but but you store complex objects using
Schema.Types.Mixed
which lacks validation in Mongoose, or you just wish
you could validate objects using a richer
JSON-schema vocabulary than is included with
Mongoose.
The Solution
The mongoose-ajv-plugin
lets you use the awesome AJV JSON-Schema
validation library, to validate individual attributes or entire
documents, giving you access to it's rich schema vocabulary and convenience
formats like email, Date, hostname, ect.
Getting Started
Attribute validation
Import Mongoose as usual:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.Promise = require('bluebird');
When validating individual attributes, it is sufficient to load the plugin globally:
mongoose.plugin(require('mongoose-ajv-plugin'));
Define a JSON-schema for your favorite attribute:
var contact_json_schema = {
"type":"object",
"properties":{
"name": {
"type":"string"
},
"email": {
"type":"string",
"fomrat":"email"
},
"birthday": {
"oneOf":[
{"$ref":"#/definitions/date"},
{"$ref":"#/definitions/date-time"}
]
}
},
"required":[
"name",
"email"
],
"definitions":{
"date":{
"type":"string",
"format":"date"
},
"date-time":{
"type":"string",
"format":"date-time"
}
}
};
Define a Mongoose schema that includes a schema
attribute, or two:
var Player_schema = new Schema({
user_name: String,
rank: Number,
ip_address: {
type: String,
schema: {
type: 'string',
format: 'ipv4'
}
},
contact: {
type: Schema.Types.Mixed ,
schema: contact_json_schema
},
});
If you didn't load the mongoose-ajv-plugin globally , you'll need to add it to your schema now:
var ajv_plugin = require('mongoose-ajv-plugin')
Player_schema.plugin(ajv_plugin);
Next, create a model and some instances, and validate the instances.
var Player = mongoose.model('Player', Player_schema);
var felix = new Player({
user_name: "Felix",
rank: 5,
ip_address: "123.45.67.89",
contact: {
name:"Jack" ,
email:"plaza626@email.com",
birthday: "1925-02-08"
}
});
var oscar = new Player({
user_name: "Oscar",
rank: 7,
ip_address: "123.4.5.678",
contact: {
name:"Walter" ,
email:"RedWingsFan@poker.com",
birthday: "October 1, 1920"
},
})
felix.validate(validate_callback_factory("Felix"))
validate_promise(felix,"Felix")
>> Felix passed validation!
oscar.validate(validate_callback_factory("Oscar"))
validate_promise(oscar,"Oscar")
>> Oscar failed validation with message: Player validation failed; 'contact' attribute does not match it's JSON-schema
* see convenience functions
section below.
Calling my_model_instance.save() will cause the validation to occur as well.
Document validation
Create a schema for your document
var team_json_schema = {
"type":"object",
"properties": {
"team_name": {
"type": "string",
"minLength": 5,
"maxLength": 30
},
"players": {
"type": "array",
"minItems": 2,
"maxItems": 10,
"items": {
"type": "string"
}
}
}
};
Then create an Mongoose schema and add the plugin, passing the schema in the
options parameter of Schema.plugin();
var Team_schema = new Schema({
team_name: String,
players: [String],
});
Team_schema.plugin(ajv_plugin,{schema:team_json_schema});
var Team = mongoose.model('Team', Team_schema);
Now Create and validate some instances:
var just_me = new Team({
"team_name": "Just Me",
"players": ["Bridget"]
})
var thursday_night_poker = new Team({
"team_name": "ThursdayNightPoker",
"players": ["Oscar","Felix","Speed","Vinnie","Roy","Murray"]
})
just_me.validate(validate_callback_factory("Just Me"))
validate_promise(just_me,"Just Me")
>> Just Me failed validation with message: Team validation failed; instance data does not match the JSON-schema
thursday_night_poker.validate(validate_callback_factory("Thursday Night Poker"))
validate_promise(thursday_night_poker,"Thursday Night Poker")
>> Thursday Night Poker passed validation!
* see convenience functions
section below.
Miscellaneous notes
-
Validation with the mongoose-ajv-plugin is invoked when calling
my_instance.save()
or my_instance.validate()
. The mongoose-ajv-plugin
,
is implemented as a model.pre('validate',...)
method, which, at the time of
this writing, is not invoked by my_instance.validateSync()
.
-
Like internal Mongoose validators, the AJV-Mongoose plugin does
not validate undefined values. To require values to be defined, use the
built in required
schema attribute.
Advanced options
If you want to use multiple schema, you can load up your own ajv instance and
pass it in the options parameter of Schema.plugin():
var AJV = require('ajv'),
ajv = new AJV();
ajv.addSchema(schema, 'mySchema');
Team_schema.plugin(ajv_plugin,{schema: team_json_schema,ajv: ajv});
Convenience Functions
function validate_callback_factory(name){
return function (err,doc){
if(err){
console.log(name +" passed validation!");
}else{
console.log(name +" failed validation with message: " + err.message);
}
};
}
function validate_promise (data,name){
data.validate().then(function(x){
console.log(name +" passed validation!");
}) .catch(function(err){
console.log(name +" failed validation with message: " + err.message);
})
}