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
| 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.
- 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
- 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.)
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' }
}- Built-in Rules
- Features
- Installation
- Usage Guide
- API Reference
- TypeScript Type Inference
- Performance
- Additional Resources
- Contributing
- License
npm install livrPre-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 |
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.
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.
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
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
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'
});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';
};
}
});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';
};
}
});// 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' }}
});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 */ });Creates a new validator instance.
const validator = new LIVR.Validator(schema, { autoTrim: true });| Option | Default | Description |
|---|---|---|
autoTrim |
false |
Trim all string values before validation |
Enable/disable auto-trim globally for all new instances.
LIVR.Validator.defaultAutoTrim(true);Register custom rules globally.
LIVR.Validator.registerDefaultRules({
my_rule(arg) {
return (value) => { /* ... */ };
}
});Register a rule alias globally.
LIVR.Validator.registerAliasedDefaultRule({
name: 'adult_age',
rules: ['positive_integer', { min_number: 18 }],
error: 'MUST_BE_ADULT' // optional custom error
});Returns all registered default rules.
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
}Returns the errors object from the last validation (sync only).
// Example output:
{
email: 'WRONG_EMAIL',
password: 'TOO_SHORT',
address: { zip: 'NOT_POSITIVE_INTEGER' }
}Pre-compiles validation rules. Called automatically on first validate(), but can be called manually for warmup.
const validator = new LIVR.Validator(schema).prepare();Register rules for this instance only.
validator.registerRules({
custom_rule() { return (value) => { /* ... */ }; }
});Register a rule alias for this instance only.
validator.registerAliasedRule({
name: 'strong_password',
rules: { min_length: 8 },
error: 'WEAK_PASSWORD'
});Returns all rules registered for this instance.
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.
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);
}- LIVR Specification - Full specification and rule documentation
- livr-extra-rules - Additional validation rules
- TypeScript Guide - Comprehensive TypeScript documentation
Found a bug or have a feature request? Please open an issue on GitHub.
MIT License - see LICENSE for details.
Viktor Turskyi (@koorchik)
- eNdiD