livr
v2.10.1
Published
Lightweight validator supporting Language Independent Validation Rules Specification
Downloads
40,145
Maintainers
Readme
LIVR Validator
Lightweight, fast, and language-independent validation for JavaScript & TypeScript
LIVR Specification - Full documentation of all validation rules
Design Decisions - Why LIVR works the way it does: schemas as data, security, error codes, and more
Built-in Rules
| Category | Rules | |----------|-------| | Common | required · not_empty · not_empty_list · any_object | | String | string · eq · one_of · max_length · min_length · length_between · length_equal · like | | Numeric | integer · positive_integer · decimal · positive_decimal · max_number · min_number · number_between | | Special | email · url · iso_date · equal_to_field | | Meta | nested_object · variable_object · list_of · list_of_objects · list_of_different_objects · or | | Modifiers | trim · to_lc · to_uc · remove · leave_only · default |
Need more rules? Check out livr-extra-rules for additional validators.
Features
Why LIVR?
- Zero dependencies - No external runtime dependencies
- Tiny footprint - Validator core < 1KB, with all rules ~3KB (min+gzip)
- TypeScript support - Full type inference from validation schemas
- Isomorphic - Works in Node.js and browsers
- Sync & async - Both synchronous and asynchronous validation
- Extensible - Easy to add custom rules and aliases
Validation Capabilities
- Declarative schemas - Rules are language-independent JSON structures
- Multiple rules per field - Chain any number of validators
- Aggregated errors - Returns all errors at once, not just the first
- Data sanitization - Output contains only validated fields
- Hierarchical validation - Validate nested objects and arrays
- Readable error codes - Returns codes like
REQUIRED,TOO_SHORT(not messages) - Output transformation - Rules can modify output (
trim,default, etc.)
Quick Start
import LIVR from 'livr';
const validator = new LIVR.Validator({
name: 'required',
email: ['required', 'email'],
age: 'positive_integer',
password: ['required', { min_length: 8 }],
password2: { equal_to_field: 'password' }
});
const validData = validator.validate(userData);
if (validData) {
// Use validated & sanitized data
saveUser(validData);
} else {
// Handle validation errors
console.log(validator.getErrors());
// { email: 'WRONG_EMAIL', password: 'TOO_SHORT' }
}Table of Contents
- Built-in Rules
- Features
- Installation
- Usage Guide
- API Reference
- TypeScript Type Inference
- Performance
- Additional Resources
- Contributing
- License
Installation
npm install livrBrowser (without npm)
Pre-built versions are available in the dist folder:
| Build | Description |
|-------|-------------|
| dist/production/main.js | Minified sync validator |
| dist/production-async/main.js | Minified async validator |
| dist/development/main.js | Development build with source maps |
| dist/development-async/main.js | Async development build |
Usage Guide
Basic Validation
import LIVR from 'livr';
// Enable auto-trim globally (optional)
LIVR.Validator.defaultAutoTrim(true);
const validator = new LIVR.Validator({
name: 'required',
email: ['required', 'email'],
gender: { one_of: ['male', 'female'] },
phone: { max_length: 10 },
password: ['required', { min_length: 10 }],
password2: { equal_to_field: 'password' }
});
const validData = validator.validate(userData);
if (validData) {
saveUser(validData);
} else {
console.log(validator.getErrors());
}Note: Rule names support both
snake_caseandcamelCase. Useone_oforoneOf,min_lengthorminLength- they're equivalent.
TypeScript with Type Inference
LIVR can automatically infer TypeScript types from your validation schema:
import LIVR from 'livr';
import type { InferFromSchema } from 'livr/types';
const userSchema = {
name: ['required', 'string'],
email: ['required', 'email'],
age: 'positive_integer',
role: { one_of: ['admin', 'user'] as const },
} as const;
// Automatically infer type from schema
type User = InferFromSchema<typeof userSchema>;
// Result: { name: string; email: string; age?: number; role?: 'admin' | 'user' }
const validator = new LIVR.Validator<User>(userSchema);
// Validate data from external source (API request, form submission, etc.)
const input = getUserInput();
const validData = validator.validate(input);
if (validData) {
// validData is typed as User
console.log(validData.name); // string
console.log(validData.age); // number | undefined
}Important: Use
as constafter your schema to enable proper type inference.
For comprehensive TypeScript documentation including nested objects, lists, unions, and custom rule types, see TypeScript Type Inference Guide.
Async Validation
For rules that require async operations (database lookups, API calls):
import LIVR from 'livr/async';
const validator = new LIVR.AsyncValidator({
username: ['required', 'unique_username'], // custom async rule
email: ['required', 'email'],
});
try {
const validData = await validator.validate(userData);
saveUser(validData);
} catch (errors) {
console.log(errors);
}Key differences from sync validator:
- Import from
'livr/async' - Use
AsyncValidatorinstead ofValidator validate()returns a Promise - useawaitor.then()- On error, rejects with errors object (no
getErrors()method) - Fields validate in parallel; rules per field run sequentially
Using Modifiers
Modifiers transform data during validation:
const validator = new LIVR.Validator({
email: ['required', 'trim', 'email', 'to_lc'], // trim, validate, lowercase
age: ['positive_integer', { default: 18 }], // default value if empty
});Available modifiers: trim, to_lc, to_uc, default, remove, leave_only
Custom Rules
Using Aliases (Recommended)
Create reusable rules by combining existing ones:
const validator = new LIVR.Validator({
password: ['required', 'strong_password'],
age: ['required', 'adult_age'],
});
validator.registerAliasedRule({
name: 'strong_password',
rules: { min_length: 8 },
error: 'WEAK_PASSWORD'
});
validator.registerAliasedRule({
name: 'adult_age',
rules: ['positive_integer', { min_number: 18 }],
error: 'MUST_BE_ADULT'
});Writing Custom Rule Functions
For complex validation logic:
const validator = new LIVR.Validator({
password: ['required', 'strong_password'],
});
validator.registerRules({
strong_password() {
return (value) => {
// Empty values are handled by 'required' rule
if (value === undefined || value === null || value === '') return;
if (!/[A-Z]/.test(value)) return 'MISSING_UPPERCASE';
if (!/[a-z]/.test(value)) return 'MISSING_LOWERCASE';
if (!/[0-9]/.test(value)) return 'MISSING_DIGIT';
if (value.length < 8) return 'TOO_SHORT';
};
}
});Async Custom Rules
import LIVR from 'livr/async';
const validator = new LIVR.AsyncValidator({
username: ['required', 'unique_username'],
});
validator.registerRules({
unique_username() {
return async (value) => {
if (value === undefined || value === null || value === '') return;
const exists = await db.users.exists({ username: value });
if (exists) return 'USERNAME_TAKEN';
};
}
});Registering Rules Globally
// Register for all future validator instances
LIVR.Validator.registerDefaultRules({
my_rule(arg1, arg2) {
return (value, allValues, outputArr) => {
// Return error code on failure, undefined on success
if (invalid) return 'ERROR_CODE';
};
}
});
LIVR.Validator.registerAliasedDefaultRule({
name: 'valid_address',
rules: { nested_object: { country: 'required', city: 'required' }}
});Tree-Shaking (Reduce Bundle Size)
Import only the rules you need:
import Validator from 'livr/lib/Validator';
Validator.registerDefaultRules({
required: require('livr/lib/rules/common/required'),
email: require('livr/lib/rules/special/email'),
min_length: require('livr/lib/rules/string/min_length'),
max_length: require('livr/lib/rules/string/max_length'),
equal_to_field: require('livr/lib/rules/special/equal_to_field'),
});
const validator = new Validator({ /* schema */ });API Reference
Static Methods
new LIVR.Validator(schema, options?)
Creates a new validator instance.
const validator = new LIVR.Validator(schema, { autoTrim: true });| Option | Default | Description |
|--------|---------|-------------|
| autoTrim | false | Trim all string values before validation |
Validator.defaultAutoTrim(boolean)
Enable/disable auto-trim globally for all new instances.
LIVR.Validator.defaultAutoTrim(true);Validator.registerDefaultRules(rules)
Register custom rules globally.
LIVR.Validator.registerDefaultRules({
my_rule(arg) {
return (value) => { /* ... */ };
}
});Validator.registerAliasedDefaultRule(alias)
Register a rule alias globally.
LIVR.Validator.registerAliasedDefaultRule({
name: 'adult_age',
rules: ['positive_integer', { min_number: 18 }],
error: 'MUST_BE_ADULT' // optional custom error
});Validator.getDefaultRules()
Returns all registered default rules.
Instance Methods
validator.validate(data)
Validates input data against the schema.
Sync Validator:
const result = validator.validate(data);
if (result) {
// result contains validated data
} else {
const errors = validator.getErrors();
}Async Validator:
try {
const result = await validator.validate(data);
} catch (errors) {
// errors object
}validator.getErrors()
Returns the errors object from the last validation (sync only).
// Example output:
{
email: 'WRONG_EMAIL',
password: 'TOO_SHORT',
address: { zip: 'NOT_POSITIVE_INTEGER' }
}validator.prepare()
Pre-compiles validation rules. Called automatically on first validate(), but can be called manually for warmup.
const validator = new LIVR.Validator(schema).prepare();validator.registerRules(rules)
Register rules for this instance only.
validator.registerRules({
custom_rule() { return (value) => { /* ... */ }; }
});validator.registerAliasedRule(alias)
Register a rule alias for this instance only.
validator.registerAliasedRule({
name: 'strong_password',
rules: { min_length: 8 },
error: 'WEAK_PASSWORD'
});validator.getRules()
Returns all rules registered for this instance.
TypeScript Type Inference
LIVR automatically infers TypeScript types from your validation schemas:
import LIVR from 'livr';
import type { InferFromSchema } from 'livr/types';
const schema = {
name: ['required', 'string'],
age: 'positive_integer',
role: { one_of: ['admin', 'user'] as const },
} as const;
type User = InferFromSchema<typeof schema>;
// { name: string; age?: number; role?: 'admin' | 'user' }For complete documentation on type inference including:
- Required vs optional fields
- Nested objects and arrays
- Discriminated unions
- Custom rule type definitions
See the TypeScript Type Inference Guide.
Performance
LIVR is designed for speed:
- Reuse validators - Construct once, validate many times.
validator.validate()is extremely fast. - Lazy compilation - Rules are compiled on first validation (or call
prepare()for warmup). - Faster than alternatives - 2x faster than Joi, 100x faster rule compilation than fastest-validator.
// Good: Create once, use many times
const validator = new LIVR.Validator(schema);
for (const item of items) {
const result = validator.validate(item);
}
// Avoid: Creating validator for each validation
for (const item of items) {
const validator = new LIVR.Validator(schema); // Slower
const result = validator.validate(item);
}Additional Resources
- LIVR Specification - Full specification and rule documentation
- livr-extra-rules - Additional validation rules
- TypeScript Guide - Comprehensive TypeScript documentation
Contributing
Found a bug or have a feature request? Please open an issue on GitHub.
License
MIT License - see LICENSE for details.
Author
Viktor Turskyi (@koorchik)
Contributors
- eNdiD
