Validator API Reference
The Validator class provides comprehensive data validation with built-in rules, custom validators, and type checking.
Table of Contents
Overview
The Validator class enables:
- Field-level validation - Validate individual fields
- Record-level validation - Validate entire records
- Built-in validators - Common validation rules (required, type, min, max, pattern)
- Custom validators - Write your own validation logic
- Type checking - Validate data types (string, number, email, URL, etc.)
- Async validation - Support for async validation functions
- Default values - Apply defaults to records
- Computed fields - Auto-computed field values
import { Validator } from 'bindra';Constructor
constructor(fields: FieldConfig<T>[])Creates a new Validator with field configurations.
Parameters:
- fields (
FieldConfig<T>[]) - Array of field configurations
Example:
interface User {
id: number;
name: string;
email: string;
age: number;
}
const validator = new Validator<User>([
{
name: 'name',
label: 'Name',
validation: {
required: true,
type: 'string',
min: 2,
max: 50
}
},
{
name: 'email',
label: 'Email',
validation: {
required: true,
type: 'email'
}
},
{
name: 'age',
label: 'Age',
validation: {
required: true,
type: 'number',
min: 18,
max: 120
}
}
]);Types
FieldConfig
Configuration for a single field.
interface FieldConfig<T> {
name: keyof T; // Field name
label?: string; // Display label
validation?: FieldValidation<T>; // Validation rules
defaultValue?: any; // Default value
computed?: (record: T) => any; // Computed function
readOnly?: boolean; // Read-only field
transform?: (value: any) => any; // Value transformer
}FieldValidation
Validation rules for a field.
interface FieldValidation<T> {
required?: boolean; // Field is required
type?: 'string' | 'number' | 'boolean' | 'date' |
'email' | 'url' | 'array' | 'object';
min?: number; // Min value/length
max?: number; // Max value/length
pattern?: RegExp; // RegEx pattern
custom?: (value: any, record: Partial<T>) =>
boolean | string | Promise<boolean | string>;
message?: string; // Custom error message
}Methods
validateField()
Validate a single field value.
async validateField(
fieldName: keyof T,
value: any,
record?: Partial<T>
): Promise<string | null>Parameters
- fieldName - Name of the field to validate
- value - Value to validate
- record - Full record for context (optional)
Returns
string- Error message if invalidnull- If valid
Example
// Validate a field
const error = await validator.validateField('email', 'invalid-email');
if (error) {
console.error(error); // "Email must be a valid email"
}
// Valid field
const error2 = await validator.validateField('email', 'user@example.com');
console.log(error2); // nullvalidateRecord()
Validate an entire record.
async validateRecord(
record: Partial<T>,
options?: { skipReadOnly?: boolean; skipComputed?: boolean }
): Promise<Record<string, string> | null>Parameters
- record - Record to validate
- options - Validation options
skipReadOnly- Skip read-only fields (default: true)skipComputed- Skip computed fields (default: true)
Returns
Record<string, string>- Object with field errorsnull- If valid
Example
const user = {
name: 'A', // Too short
email: 'invalid',
age: 15 // Too young
};
const errors = await validator.validateRecord(user);
if (errors) {
console.log(errors);
// {
// name: "Name must have at least 2 characters",
// email: "Email must be a valid email",
// age: "Age must be at least 18"
// }
}applyDefaults()
Apply default values to a record.
applyDefaults(record: Partial<T>): Partial<T>Parameters
- record - Partial record
Returns
Record with defaults applied
Example
const validator = new Validator<User>([
{
name: 'role',
defaultValue: 'user'
},
{
name: 'active',
defaultValue: true
}
]);
const user = { name: 'Alice' };
const withDefaults = validator.applyDefaults(user);
console.log(withDefaults);
// { name: 'Alice', role: 'user', active: true }applyComputed()
Apply computed fields to a record.
applyComputed(record: T): TParameters
- record - Record to compute fields for
Returns
Record with computed fields
Example
const validator = new Validator<User>([
{
name: 'fullName',
computed: (record) => `${record.firstName} ${record.lastName}`
},
{
name: 'isAdult',
computed: (record) => record.age >= 18
}
]);
const user = {
firstName: 'Alice',
lastName: 'Smith',
age: 25
};
const withComputed = validator.applyComputed(user);
console.log(withComputed);
// {
// firstName: 'Alice',
// lastName: 'Smith',
// age: 25,
// fullName: 'Alice Smith',
// isAdult: true
// }getField()
Get field configuration by name.
getField(name: keyof T): FieldConfig<T> | undefinedParameters
- name - Field name
Returns
Field configuration or undefined
Example
const nameConfig = validator.getField('name');
if (nameConfig) {
console.log(nameConfig.label); // "Name"
console.log(nameConfig.validation?.required); // true
}hasField()
Check if a field exists in the validator.
hasField(name: keyof T): booleanParameters
- name - Field name
Returns
boolean - True if field exists
Example
if (validator.hasField('email')) {
console.log('Email field is configured');
}Validation Rules
Required
Field must have a value.
{
name: 'username',
validation: {
required: true,
message: 'Username is required'
}
}Type Validation
Validate data types.
Available types:
string- String valuenumber- Numeric valueboolean- Boolean valuedate- Valid dateemail- Valid email addressurl- Valid URLarray- Arrayobject- Object
{
name: 'email',
validation: {
type: 'email',
message: 'Must be a valid email'
}
}Min/Max
Minimum and maximum values or lengths.
// Number range
{
name: 'age',
validation: {
type: 'number',
min: 18,
max: 120
}
}
// String length
{
name: 'username',
validation: {
type: 'string',
min: 3,
max: 20
}
}
// Array length
{
name: 'tags',
validation: {
type: 'array',
min: 1,
max: 5
}
}Pattern (RegEx)
Validate with regular expressions.
{
name: 'phone',
validation: {
pattern: /^\d{3}-\d{3}-\d{4}$/,
message: 'Phone must be in format: 123-456-7890'
}
}Custom Validators
Write custom validation logic.
// Synchronous
{
name: 'password',
validation: {
custom: (value) => {
if (value.length < 8) return 'Too short';
if (!/[A-Z]/.test(value)) return 'Must have uppercase';
if (!/[0-9]/.test(value)) return 'Must have number';
return true; // Valid
}
}
}
// Asynchronous (e.g., check if username exists)
{
name: 'username',
validation: {
custom: async (value) => {
const exists = await checkUsernameExists(value);
return exists ? 'Username already taken' : true;
}
}
}
// With record context
{
name: 'confirmPassword',
validation: {
custom: (value, record) => {
return value === record.password || 'Passwords must match';
}
}
}Usage Examples
Basic Validation
import { Validator } from 'bindra';
interface User {
id: number;
name: string;
email: string;
age: number;
}
const validator = new Validator<User>([
{
name: 'name',
validation: {
required: true,
type: 'string',
min: 2
}
},
{
name: 'email',
validation: {
required: true,
type: 'email'
}
},
{
name: 'age',
validation: {
required: true,
type: 'number',
min: 18
}
}
]);
// Validate a record
const user = {
name: 'Alice',
email: 'alice@example.com',
age: 25
};
const errors = await validator.validateRecord(user);
if (errors) {
console.error('Validation failed:', errors);
} else {
console.log('Validation passed!');
}With DataSource Integration
import { DataSource, Validator } from 'bindra';
interface User {
id: number;
name: string;
email: string;
age: number;
}
const validator = new Validator<User>([
{
name: 'name',
validation: { required: true, min: 2 }
},
{
name: 'email',
validation: { required: true, type: 'email' }
}
]);
const users = new DataSource<User>({
url: '/api/users',
validation: {
validator: validator,
validateOnCreate: true,
validateOnUpdate: true
}
});
// Create will automatically validate
try {
const newUser = await users.create({
name: 'A', // Too short!
email: 'invalid'
});
} catch (error) {
console.error(error); // ValidationError
}Form Validation
const formValidator = new Validator<FormData>([
{
name: 'username',
label: 'Username',
validation: {
required: true,
min: 3,
max: 20,
pattern: /^[a-zA-Z0-9_]+$/,
message: 'Username must be 3-20 alphanumeric characters'
}
},
{
name: 'password',
label: 'Password',
validation: {
required: true,
custom: (value) => {
if (value.length < 8) return 'Minimum 8 characters';
if (!/[A-Z]/.test(value)) return 'Must contain uppercase';
if (!/[a-z]/.test(value)) return 'Must contain lowercase';
if (!/[0-9]/.test(value)) return 'Must contain number';
return true;
}
}
},
{
name: 'confirmPassword',
label: 'Confirm Password',
validation: {
required: true,
custom: (value, record) => {
return value === record.password || 'Passwords do not match';
}
}
},
{
name: 'email',
label: 'Email',
validation: {
required: true,
type: 'email'
}
}
]);
// Validate form submission
const formData = {
username: 'alice',
password: 'SecurePass123',
confirmPassword: 'SecurePass123',
email: 'alice@example.com'
};
const errors = await formValidator.validateRecord(formData);
if (!errors) {
console.log('Form is valid!');
// Submit form
}Async Validation
const validator = new Validator<User>([
{
name: 'username',
validation: {
required: true,
custom: async (value) => {
// Check if username is already taken
const response = await fetch(`/api/check-username?name=${value}`);
const { available } = await response.json();
return available || 'Username already taken';
}
}
},
{
name: 'email',
validation: {
required: true,
type: 'email',
custom: async (value) => {
// Check if email is already registered
const response = await fetch(`/api/check-email?email=${value}`);
const { available } = await response.json();
return available || 'Email already registered';
}
}
}
]);With Defaults and Computed Fields
interface Order {
id: number;
items: Item[];
subtotal: number;
tax: number;
total: number;
discount: number;
status: string;
createdAt: Date;
}
const validator = new Validator<Order>([
{
name: 'status',
defaultValue: 'pending'
},
{
name: 'discount',
defaultValue: 0
},
{
name: 'subtotal',
computed: (order) => {
return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
},
{
name: 'tax',
computed: (order) => order.subtotal * 0.08
},
{
name: 'total',
computed: (order) => order.subtotal + order.tax - order.discount
},
{
name: 'createdAt',
defaultValue: () => new Date(),
readOnly: true
}
]);
// Apply defaults
const order = { items: [{ price: 100, qty: 2 }] };
const withDefaults = validator.applyDefaults(order);
// Apply computed fields
const complete = validator.applyComputed(withDefaults);
console.log(complete);
// {
// items: [...],
// status: 'pending',
// discount: 0,
// subtotal: 200,
// tax: 16,
// total: 216,
// createdAt: Date(...)
// }Best Practices
1. Use Descriptive Labels
// ✅ Good
{
name: 'email',
label: 'Email Address',
validation: { type: 'email' }
}
// ❌ Bad
{
name: 'email',
validation: { type: 'email' }
}2. Provide Custom Error Messages
// ✅ Good
{
name: 'age',
validation: {
min: 18,
message: 'You must be 18 or older to register'
}
}
// ❌ Bad - Generic message
{
name: 'age',
validation: { min: 18 }
}3. Use Async Validators Sparingly
// ✅ Good - Only when necessary
{
name: 'username',
validation: {
custom: async (value) => {
// Only check on server if local validation passes
if (value.length < 3) return 'Too short';
return await checkAvailability(value);
}
}
}4. Combine Built-in and Custom Validators
// ✅ Good - Use built-ins first
{
name: 'password',
validation: {
required: true,
type: 'string',
min: 8,
custom: (value) => {
// Additional custom checks
return /[A-Z]/.test(value) || 'Must contain uppercase';
}
}
}5. Handle Validation Errors Gracefully
// ✅ Good
const errors = await validator.validateRecord(data);
if (errors) {
// Show errors to user
Object.entries(errors).forEach(([field, message]) => {
showFieldError(field, message);
});
} else {
// Proceed with data
await submitData(data);
}Related Documentation
- DataSource - DataSource with validation
- Getting Started - First steps
- Examples - Usage examples