Declarative validation with async validators support. If you also want to work with filters and default values, take a look at transformer-chain. You may be interested in express middleware (express-request-parameters) useful for HTTP API.
Note: This module works in browsers and Node.js >= 4.0. Use Object.assign
and Promise
polyfills for Internet Explorer
Try demo on RunKit.
npm install validy
const validy = require('validy');
<script src="node_modules/validy/dist/validy.js">
or minified version
<script src="node_modules/validy/dist/validy.min.js">
You can use the module with AMD/CommonJS or just use window.validy
.
validy
allows you to validate flat and nested objects using collection of default validators and your own validators.
Validators can be asynchronous, you can do DB calls for example and so on.
To validate object you should define schema. It's simple object with your constraints:
const book = { // object to validate
name: 'The Adventures of Tom Sawyer',
author: {
name: 'Mark Twain'
},
reviews: [
{
author: 'Leo Tolstoy',
text: 'Great novel'
},
{
author: 'Fyodor Dostoyevsky',
text: 'Very interesting'
}
]
};
const schema = {
name: {
$validate: {
required: true,
string: true
}
},
author: {
$validate: { // you can omit check that "author" value is object, it will be done internally
required: true
},
name: {
$validate: {
required: true,
string: true
}
}
},
reviews: [{ // define schema for array items
author: {
$validate: {
required: true,
string: true
}
},
text: {
$validate: {
required: true,
string: true
}
}
}]
};
validy(book, schema)
.then(errors => {
if (errors) {
// you have validation errors ("errors" is plain object)
} else {
// no errors ("errors" is undefined)
}
})
.catch(err => {
// application error (something went wrong)
});
// async/await example
async function example() {
try {
const errors = await validy(book, schema);
if (errors) {
// you have validation errors ("errors" is plain object)
} else {
// no errors ("errors" is undefined)
}
} catch(err) {
// application error (something went wrong)
}
}
object
(Object) - Object to validateschema
(Object) - Schema which defines how to validate object[options]
(Object) - Validation options[format=flat]
(string) - Format of object with validation errors (flat
,nested
)[reject=false]
(boolean) - Should return fulfilled promise with errors (by default
) or rejected withValidationError
?
(Promise) - Result of validation. Promise is returned even for synchronous only validation
By default validy
uses collection of simple and useful validators (common-validators module).
Note:
The basic principle of built-in validators is that most of them (except required
, notEmpty
and type validators object
, array
, ...) consider empty values as valid values.
Empty values are:
undefined
null
NaN
''
' '
(whitespace only string)[]
{}
Also they convert passed value to expected type. For example, max
validator which checks that value is not greater than some limit will try to convert passed value to number (Number(value)
).
All non-convertible values will be treated as NaN
and validator will return validation error.
Some of built-in validators:
- required (presence) - validates that the value isn't
undefined
,null
,NaN
, empty or whitespace only string, empty array or object - notEmpty - like
required
butundefined
is valid value. It is useful for PATCH requests - object / array / string / number / integer / ... - value is plain object, array, etc.
undefined
is valid value - max - value is less than maximum
- min - value is greater than minimum
- range - value is in range
- maxLength - value length is greater than maximum
- minLength - value length is greater than minimum
- pattern - value matches the pattern
- inclusion - value is contained in white list
- exclusion - value is not contained in black list
- email - value is email address
- url - value is URL
- and many others (see common-validators#validators)
You can add your own validator:
validy.validators.add('greaterThan', function(value, options) {
// validator implementation
});
// or
validy.validators.add({ // this way you can add several validators at once
greaterThan: function(value, options) {
// validator implementation
},
anotherValidator: function(value, options) {
// validator implementation
}
});
Although in most cases you will have only two parameters in your own validators (value
and options
), some situations will require a bit knowledgeable validator.
So, full signature of validator is:
validator(value, options, object, fullObject, path)
value
(any) - Validated valueoptions
(Object) - Validator optionsobject
(Object) - Object whose property is validated at the momentfullObject
(Object) - The whole validated object (object which was initially passed tovalidy
)path
(string[]) - Path to property
So imagine you wrote validateSomething
validator:
const book = {
name: 'The Adventures of Tom Sawyer',
author: {
name: 'Mark Twain'
}
};
const schema = {
name: {
$validate: {
required: true,
string: true
}
},
author: {
$validate: {
required: true
},
name: {
$validate: {
required: true,
string: true,
validateSomething: 'someArgument' // <--- and you want to use it here
}
}
}
};
validateSomething
validator will be called with the following arguments:
- value
Value of author.name
property.
'Mark Twain'
- options
When you use non-object value as validator options it will be wrapped in object with arg
property.
{
arg: 'someArgument'
}
- object
Object with name
property (author
object).
{
name: 'Mark Twain'
}
- fullObject
The whole validated object (book
object).
{
name: 'The Adventures of Tom Sawyer',
author: {
name: 'Mark Twain'
}
}
- path
['author', 'name']
Example:
validy.validators.add('russianLettersOnly', function(value) {
// it's ok to consider empty value as valid value
// use "required" validator when this value must not be empty
if (value === '') {
// if value is valid just return nothing or falsy value
return;
}
if (typeof value !== 'string' || !/^[Đ°-ŃŹŃ‘]+$/i.test(value)) {
// when value is invalid, return string with error
return 'must contain only Russian letters';
}
});
// or
validy.validators.add({ // this way you can add several validators at once
russianLettersOnly: function(value) { /**/ },
anotherValidator: function(value) { /**/ }
});
And then just use it as any other validator:
{
name: {
$validate: {
// validator will be called only if its config is not equal to false/null/undefined
russianLettersOnly: true
}
}
}
Almost the same as synchronous validator, just return fulfilled promise:
validy.validators.add({
/**
* Check using mongoose model that value exists in mongodb
*
* @param {string} value
* @param {Object} options
* @param {Object} options.model - Mongoose model
* @param {string} [options.field] - Which field to use for search
*
* @returns {Promise}
*/
exists: function(value, options) {
const errorMessage = 'does not exist';
if (value === '') {
return Promise.resolve();
}
if (typeof value !== 'string') {
return Promise.resolve(errorMessage);
}
const model = options.model;
const field = options.field || '_id';
return model.count({ [field]: value })
.then(count => {
if (!count) {
// if value is invalid, return fulfilled promise with validation error
return Promise.resolve(errorMessage);
}
});
}
});
If there are no validation errors validy
returns undefined
as fulfillment value:
validy(book, schema)
.then(errors => {
console.log(errors); // undefined
})
If you have validation errors:
const book = {
name: '', // empty
author: {
name: 123456789 // not string
}
};
const schema = {
name: {
$validate: {
required: true,
string: true
}
},
author: {
$validate: {
required: true
},
name: {
$validate: {
required: true,
string: true
}
}
}
};
validy(book, schema)
.then(errors => {
console.log(errors);
})
errors
has flat structure by default:
{
name: [{ // errors are always placed in array
error: 'required', // validator name
message: 'Is required' // error message
}],
'author.name': [{
error: 'string',
message: 'Must be a string'
}]
}
But you can use nested structure:
validy(book, schema, { format: 'nested' })
.then(errors => {
console.log(errors);
})
errors
with nested structure:
{
name: [{
error: 'required',
message: 'Is required'
}],
author: {
name: [{
error: 'string',
message: 'Must be a string'
}]
}
}
You can customize error message for specific property:
const schema = {
url: {
$validate: {
url: { // instead of { url: true }
message: 'invalid url' // message can be either string or function returning string
}
}
}
};
const object = {
url: 'not-url'
};
validy(object, schema)
.then(errors => {
console.log(errors); // { url: [ { error: 'url', message: 'invalid url' } ] }
});
Or set default message for a validator:
validy.validators.url.defaultOptions = { message: 'bad url' };
const schema = {
url: {
$validate: {
url: true
}
}
};
const object = {
url: 'not-url'
};
validy(object, schema)
.then(errors => {
console.log(errors); // { url: [ { error: 'url', message: 'bad url' } ] }
});
By default validy
returns fulfilled promise when validated object is not valid.
If for some reasons you want to use rejected promise with validation error instead of fulfilled promise, specify reject
option:
validy(object, schema, { reject: true })
.then(() => {
// no errors, everything is valid
})
.catch(err => {
if (err instanceof validy.ValidationError) {
// err.errors contains validation errors
} else {
// application error (something went wrong)
}
});
Sometimes you may need a way to validate some property differently depending on specific conditions. Example with order of various products:
const order = {
products: [
{
type: 'book',
name: 'The Adventures of Tom Sawyer',
count: 1
},
{
type: 'sugar',
weight: 3000
}
]
};
const productsSchemas = {
book: {
name: {
$validate: {
required: true,
string: true
}
},
count: {
$validate: {
required: true,
integer: true,
min: 1
}
}
},
sugar: {
weight: {
$validate: {
required: true,
integer: true,
min: 1000
}
}
}
};
const schema = {
products: [(product/*, products, order, pathToItem*/) => {
const productSchema = productsSchemas[product.type];
return Object.assign({}, productSchema, {
type: {
$validate: {
required: true,
string: true,
inclusion: Object.keys(productsSchemas)
}
}
});
}]
};
// or you can do like this (products is marked as required)
const alternativeSchema = {
products: {
$validate: { // validate also "products" before items validation
required: true,
array: true
},
$items: function(product/*, products, order, pathToItem*/) {
const productSchema = productsSchemas[product.type] || {};
return Object.assign({}, productSchema, {
type: {
$validate: {
required: true,
string: true,
inclusion: Object.keys(productsSchemas)
}
}
});
}
}
};
You can do similar things with $validate
and specific validator:
const bookSchema = {
author: {
name: {
$validate: function(name, author, book, pathToName) {
// implement your custom logic
// validation will only run if you return object
// so you can return null for example to skip validation
return {
required: function(name, author, book, pathToName) {
// implement your custom logic
// return undefined, null or false if you want skip validation
},
string: true
};
}
}
}
};
npm install
npm run build
npm install
npm test