Node Input Validator or NIV is a validation library for NodeJs, inspired by Laravel. Node Input Validator has a vast collection of inbuilt rules. This package is fully customizable or expendable with rules, messages, nicenames (used to change attributes in error messages) etc.
Many times the NIV collection of rules would be sufficient for the project, But sometimes even in small projects you may need to validate some attribute under custom conditions, there may be database involvement or you may have another unique problem that NIV rules are unable to satisfy. Don't be sad :( as i told you node input validator or NIV is fully customizable or expendable, you can add your own custom rules to it.
Even when you are good with the NIV rules collection but not happy with the error messages or you want to show messages in your own locale, node-input-validator allows you to do that also. The purpose of this post is to guide you on how to use and extend node input validator by adding your own custom validation rules, custom messages or messages in your locale, changing attribute names in error messages etc.
In this post, I have used Version 4x.
Installation
Installing node-input-validator fairly easy with npm.
npm i node-input-validator
Usage
You can use node input validator with any Node.js framework or within Node.js runtime.
const { Validator } = require('node-input-validator');
const v = new Validator(
{ name: 'Harcharan Singh' },
{ name: 'required|minLength:5' },
);
v.check().then((matched) => {
if (!matched) {
// validation failed, log errors
console.log(v.errors);
}
});
Validator accepts 3 arguments, first is inputs, second is rules and third one is optional for custom messages for the instance only.
class Validator {
constructor(
inputs:{},
rules:{},
customMessages?:{},
);
}
You may be using express
const { Validator } = require('node-input-validator');
app.post('login', function (req, res) {
const v = new Validator(req.body, {
email: 'required|email',
password: 'required'
});
v.check().then((matched) => {
if (!matched) {
res.status(422).send(v.errors);
}
// your login logic
});
});
If your are using Koa or with async/await
const { Validator } = require('node-input-validator');
router.post('login', async (ctx) => {
const v = new Validator(ctx.request.body, {
email: 'required|email',
password: 'required'
});
const matched = await v.check();
if (!matched) {
ctx.status = 422;
ctx.body = v.errors;
return;
}
// your login logic
});
Node input validator also provide you Koa middleware
const niv = require('node-input-validator');
// app.use( your error handling logic)
// keep this after your error handler
app.use(niv.koa());
Your controller would look like
router.post('login', async (ctx) => {
// this method is provided by node input validator
// this will auto throw error with http status 422 in case validation failed
await ctx.validate({
email: 'required|email',
password: 'required'
});
// your login logic
});
You may want to pass custom input or messages to validator
await ctx.validate({
email: 'required|email',
password: 'required'
}, customInput, { email: 'E-mail is required' });
NIV also supports nested input.
const { Validator } = require('node-input-validator');
const v = new Validator(
{
product: {
name: '',
price: '',
},
},
{
product: 'required|object',
'product.name': 'required|string|maxLength:100',
'product.price': 'required|numeric',
},
);
v.check().then((matched) => {
if (!matched) {
// validation failed, log errors
console.log(v.errors);
}
});
Example of deeply nested input
const v = new Validator(
{
user: {
name: '',
address: {
city: '',
state: '',
},
contacts: [
{ type: 'email', value: '' },
{ type: 'phone', value: '' },
],
roles: [],
}
},
{
user: 'required|object',
'user.name': 'required|string',
'user.address': 'required|object',
'user.address.city': 'required|string',
'user.address.state': 'required|string',
'user.contacts': 'required|array',
'user.contacts.*.type': 'required|string|in:email,phone',
'user.contacts.*.value': 'required|string',
'user.roles': 'required|array',
'user.roles.*': 'required|string|in:admin,manager,driver',
},
);
Error messages seem nonsense? like: "The user.name field is mandatory."
You can fix that too with the “nicenames” feature.
v.niceNames({
'user.name': 'user name',
'user.contacts.*.value': 'user contact value',
'user.roles': 'user role',
});
Now "The user name field is mandatory.", makes sense!. Will talk about nice names more later.
Sometimes under special scenarios, string notation rules do not play well.
Example:
new Validator(
{ nameAndId: 'Harcharan' },
{ nameAndId: 'required|regex:^[A-Za-z]+:[0-9]+$' },
);
As per regular expression, validation should fail, But the code snippet will not work as expected. Cause to parse string notation rules, validator internally break string by pipe(|) to collect rules and then further split the rule by colon(:) to collect rule arguments or seeds. So regex in the upper code snippet will be parsed as "^[A-Za-z]+".
But don't be sad :(, The validator allows you to declare rules as an array to deal with such situations.
new Validator(
{ nameAndId: 'Harcharan' },
{ nameAndId: ['required', ['regex', '^[A-Za-z]+:[0-9]+$']] },
);
or you can declare seeds as an array as well.
new Validator(
{ nameAndId: 'Harcharan' },
{ nameAndId: ['required', ['regex', ['^[A-Za-z]+:[0-9]+$']]] },
);
Both code snippets will work as expected.
Add Custom Validation Rules
In this section we will add a custom validation rule that will pass attributes under validation only if it carries value an even number.
const niv = require('node-input-validator');
niv.extend('even', ({ value }) => {
if ((parseInt(value) % 2) == 0) {
return true;
}
return false;
});
Rules that depends on another attributes
Sometimes we need to code a rule that depends on another attribute. For example, we need a rule that makes sure that the sum of 2 attributes should be as given in the rule. Let's say the sum should be 100.
niv.extend('sumOfFields', ({ value, args }, validator) => {
if (args.length !== 2) {
throw new Error('Invalid seed for rule sumOfFields');
}
// get dependent attribute value
const anotherValue = Number(validator.inputs[args[0]]);
const eq = Number(args[1]);
if ((Number(value) + anotherValue) !== eq) {
return false;
}
return true;
});
Let's see how to use this rule:
new niv.Validator(
{ attr1: '50', attr2: '50' },
{ attr1: 'sumOfFields:attr2,100|required' },
);
This rule will only pass if attr1 & attr2 sum is 100. In this example sumOfFields is our validation rule that accepts 2 arguments, first one is the name of another attribute and second is the sum that we need.
Asynchronous Rules
There are some situations, in which we need to talk to some other process or i/o and that communication may be asynchronous. we may need to validate some input based on that other process. That process may be MySQL or MongoDb. Let's take an example where we want a rule to take care that user email should be unique. For this you may need to talk to the database server, Assume that database server to be MongoDb and you are using mongoose.
niv.extend('uniqueUserEmail', async ({ value, args }) => {
const condition = {
email: value
};
const emailExist = await mongoose.model('User')
.findOne(condition)
.select('email');
// email already exists
if (emailExist) {
return false;
}
return true;
});
Let's see how to use this rule:
new niv.Validator(
{ email: 'email@example.com'},
{ email: 'required|email|uniqueUserEmail' },
);
What if you have to make a common unique rule, which you can use with any model and any field of database.
niv.extend('unique', async ({ value, args }) => {
if (args.length !== 2) {
throw new Error('Invalid seed for rule unique');
}
const model = args[0]; // Mongoose model name
const field = args[1]; // model field/attribute name
const condition = {};
condition[filed] = value;
const exists = await mongoose.model(model)
.findOne(condition)
.select(field);
// field with same value already exists
if (exists) {
return false;
}
return true;
});
Let's see how to use this rule:
new niv.Validator(
{ email: 'email@example.com'},
{ email: 'required|email|unique:User,user_email' },
);
In some cases, You may want the field to be unique under special conditions. Let's take an example, in register user api, email is expected to be unique as we want only one entry against email or only one user account per email. In update profile api, email uniqueness should be excluding that user who is updating the profile to allow api to accept unchanged email with ease. You are free to create two or more separate rules for each problem. For demonstration purposes let's do it in one.
niv.extend('unique', async ({ value, args }) => {
if (args.length !== 2) {
throw new Error('Invalid seed for rule unique');
}
const model = args[0]; // Model name
const field = args[1]; // Model field/attribute name
const condition = {};
condition[filed] = value;
// ignore?
if (args[2]) {
condition['_id'] = { $ne: mongoose.Types.ObjectId(args[2]) };
}
const exists = await mongoose.model(model)
.findOne(condition)
.select(field);
// field with same value already exists
if (exists) {
return false;
}
return true;
});
Let's see how to use this rule:
// email should not exists in users collection
new niv.Validator(
{ email: 'email@example.com'},
{ email: 'required|email|unique:User,user_email' },
);
// email should not exists in users collection is changed, will allow unchanged email
new niv.Validator(
{ email: 'email@example.com'},
{ email: 'required|email|unique:User,user_email,<user _id here>' },
);
Rule messages
The rules that we added, do not show appropriate validation error messages. We need to add messages for our custom rules.
Note: below example of messages is based on the “unique” rule.
const niv = require('node-input-validator');
// The email has already been taken.
niv.extendMessages({
unique: 'The :attribute has already been taken.',
});
Whenever validation fails against "unique" rule, message in validation errors will be: The email has already been taken. In output :attribute will be replaced with attribute name.
NIV has several placeholders, Placeholders are defined with colon (:).
Attribute (:attribute)
This will be replaced with the attribute name. :attribute will take care of both snakecase and camelCase attributes. For example: useremail or userEmail will be rendered as 'user email' in the output message.
Value (:value)
This will be replaced with attribute value. For example, if we define our rule message like:
niv.extendMessages({
unique: 'The :attribute :value has already been taken.',
});
Output will be "The email email@example.com has already been taken."
Arg (:arg1,:arg2,…:arg10)
These will be replaced with rule seed.
niv.extendMessages({
unique: 'The :value in :arg1 under field :arg1 already exists.',
});
Output will be "The email@example.com in User under field user_email already exists."
Args (:args)
Sometimes we have to show all the seed that is given to a rule
niv.extendMessages({
unique: 'The validation for :attribute is failed with :args.',
});
Output will be "The validation for email is failed with User,user_email,
Wait, what if you want to override the rule message. Like in case you need to show your own custom message.
niv.addCustomMessages({
email: 'The email is already taken.'
});
Now no matter what the validation rule is. Output will be 'The email is already taken.'. I know the example doesn't make any sense, instead of this you just want to change it for “unique” rule only.
niv.addCustomMessages({
'email.unique': 'The email is already taken.'
});
Now this message will only be shown in case unique validation failed.
Adding messages
You can also add custom messages to the validation itself. These messages will only be applicable to the current instance.
const { Validator } = require('node-input-validator');
const v = new Validator(
{},
{
email: 'required|email',
password: 'required|alphaNumericDash',
},
{
// if email validation failed against required rule this message will be used
'email.required': 'E-mail is required.',
// no matter validation failed against which rule
// message always will be: Password is required
password: 'Password is required',
}
);
v.check().then((passed) => {
if (!passed) {
console.log(v.errors);
}
});
Adding messages globally
const niv = require('node-input-validator');
niv.addCustomMessages({
// if email validation failed against required rule this message will be used
'email.required': 'E-mail is required.',
// no matter validation failed against which rule
// message always will be: Password is required
password: 'Password is required',
});
These messages will be applicable to all validator instances, as defined in the global scope.
Validator user below ordering on messages
- first will check for local messages (instance level messages)
- <attribute name>.<rule name>
- <attribute name>
- <rule name>
- next it will check for global custom messages
- <attribute name>.<rule name>
- <attribute name>
- <rule name>
- if no appropriate message found in local and global bucket,than will use rule messages bucket
- <rule name>
Messages in your locale
In the below code snippet, I have added a message in punjabi language.
const niv = require('node-input-validator');
// add messages in your locale
niv.extendMessages({
required: ':attribute ਫੀਲਡ ਖਾਲੀ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ.',
}, 'pb');
To change default language
// set default language as punjabi
niv.setLang('pb');
Currently at the time of writing this post, NIV supports english, farsi(persian), brazilian.
Customize attributes names using “nicenames”
Sometime attribute names we declare in the request body seem nonsense to the user. For example: suppose we have a 'dob' attribute in our request and validation failed message is 'The dob field is invalid'. You may want to use the term "date of birth" instead of "dob" in the error message. NIV allow you to change attribute names in validation message via “nicenames” feature.
// define nicenames globally
niv.niceNames({
dob: 'Date of Birth',
});
Now the failed message will look like "The date of birth field is invalid."
Nicenames can be also defined in local scope (valid for the current instance).
const v = new niv.Validator(
req.body,
{ dob: ...rules },
);
// this will only change "dob" to "Date of birth" in this instance error messages
v.niceNames({
dob: 'Date of birth',
})
For rules, check official documentation https://www.npmjs.com/package/node-input-validator#rules
Beside rules, node input validator has another cool feature "post validation rules". With post validation rules, you can validate constraints of whole input.
Example:
// will fail as there is no attribute present from any rule
const v = new Validator(
{ },
{ '*': 'any:name,surname' },
);
v.check().then(function (matched) {
console.log(matched);
console.log(v.errors);
});
Post rules can be defined with "*", rule "any" means any one from the given fields should be present.
// will pass as one attribute is present that is required by post rule "any"
const v = new Validator(
{ name: 'Harcharan' },
{ '*': 'any:name,surname' },
);
v.check().then(function (matched) {
console.log(matched);
console.log(v.errors);
});
If post rule "all" was used instead of "any", it should fail as post rule "all" requires all the given attributes to be present.
// will fail as one attribute is missing that is required by post rule "all"
const v = new Validator(
{ name: 'Harcharan' },
{ '*': 'all:name,surname' },
);
v.check().then(function (matched) {
console.log(matched);
console.log(v.errors);
});
Post rule "all" validation should pass with below code snippet
const v = new Validator(
{ name: 'Harcharan', surname: 'Singh' },
{ '*': 'all:name,surname' },
);
v.check().then(function (matched) {
console.log(matched);
console.log(v.errors);
});
If you face any issue, you can report that on node input validator github issues. Node input validator community is welcoming to every one and is open-minded, follows GitHub Community Guidelines. Please don't hesitate to open an issue on github.
Common Issues
Error: Validation Rule: does not exist.
This means you may have typo in rule name or rule may not exist. First check if the rule exists in official documentation or if it is a custom rule then it may be a typo issue.
TypeError: Expected a string but received a undefined
This may be due to missing seed.
Example: Rule minLength requires a value to compare length.
const v = new Validator(
{ name: 'Harcharan Singh' },
{ name: 'required|minLength' },
);
Error: Seed in minLength rule for name must be a number
This may be due to invalid seed.
Example: Rule minLength requires an integer value to compare length.
const v = new Validator(
{ name: 'Harcharan Singh' },
{ name: 'required|minLength:a' },
);
Error: The number of arguments for between in the field name are invalid.
This may be due to missing seeds.
Example: Rule between seeds missing.
const v = new Validator(
{ },
{ age: 'required|between' },
);
Max(1000) repetition was reached
NIV limits nested json iteration upto 1000. if you get "Max(1000) repetition was reached", means your given input needs more iteration. You can change this limit.
const niv = require('node-input-validator');
niv.setStrNotationRepetition(2000);
For node input validator official documentation, click here..
Have any question or suggestion, please leave a comment.
Comments (0)
Login or create an account to leave a comment.
No comments yet. Be the first!