Examples & Patterns
Learn common validation patterns and see practical examples of how to use Luq in real applications.
What you'll learn
Basic Patterns
Nested objects, arrays, conditional validation
Advanced Patterns
Data transformation, custom business rules
Error Handling
Structured errors, form integration
Real-World
E-commerce, user registration examples
Basic Patterns
Nested Objects
Validate complex nested object structures using dot notation to access deeply nested fields:
type UserProfile = {
user: {
name: string;
email: string;
};
settings: {
notifications: boolean;
theme: 'light' | 'dark';
};
};
const validator = Builder()
.use(requiredPlugin)
.use(stringMinPlugin)
.use(stringEmailPlugin)
.use(objectPlugin)
.use(oneOfPlugin)
.for<UserProfile>()
.v('user', b => b.object.required())
.v('user.name', b => b.string.required().min(2))
.v('user.email', b => b.string.required().email())
.v('settings', b => b.object.required())
.v('settings.notifications', b => b.boolean.required())
.v('settings.theme', b => b.string.required().oneOf(['light', 'dark']))
.build();
Arrays and Collections
Handle array validation with length constraints, uniqueness checks, and more:
type Product = {
id: string;
name: string;
tags: string[];
prices: number[];
};
const validator = Builder()
.use(requiredPlugin)
.use(arrayMinLengthPlugin)
.use(arrayMaxLengthPlugin)
.use(arrayUniquePlugin)
.use(stringMinPlugin)
.use(numberMinPlugin)
.for<Product>()
.v('id', b => b.string.required())
.v('name', b => b.string.required())
.v('tags', b => b.array.required().minLength(1).maxLength(5).unique())
.v('tags[*]', b => b.string.required().min(1))
.v('prices', b => b.array.required().minLength(1))
.v('prices[*]', b => b.number.required().min(0))
.build();
Conditional Validation
Implement business logic where field requirements depend on other field values:
type Order = {
type: 'personal' | 'business';
companyName?: string;
taxId?: string;
};
const validator = Builder()
.use(requiredPlugin)
.use(requiredIfPlugin)
.use(optionalPlugin)
.use(stringMinPlugin)
.use(stringPatternPlugin)
.use(oneOfPlugin)
.for<Order>()
.v('type', b => b.string.required().oneOf(['personal', 'business']))
.v('companyName', b =>
b.string.requiredIf((data) => data?.type === 'business').min(2)
)
.v('taxId', b =>
b.string
.requiredIf((data) => data?.type === 'business')
.pattern(/^[0-9]{2}-[0-9]{7}$/)
)
.build();
Advanced Patterns
Data Transformation
Transform data during validation, such as normalizing input or converting types:
type FormData = {
email: string;
age: string; // comes from form as string
};
type ProcessedData = {
email: string; // lowercase transformed
age: number; // transformed to number
};
const validator = Builder()
.use(requiredPlugin)
.use(stringEmailPlugin)
.use(transformPlugin)
.for<FormData>()
.v('email', b =>
b.string
.required()
.email()
.transform((email) => email.toLowerCase())
)
.v('age', b =>
b.string
.required()
.transform((ageStr) => parseInt(ageStr, 10))
)
.build();
// ⚠️ IMPORTANT: Use parse() for transformed values, not validate()
const formData = { email: 'USER@EXAMPLE.COM', age: '25' };
// validate() returns original values
const validateResult = validator.validate(formData);
if (validateResult.isValid()) {
const data = validateResult.unwrap();
// data.email = 'USER@EXAMPLE.COM' (original)
// data.age = '25' (still string)
}
// parse() returns transformed values
const parseResult = validator.parse(formData);
if (parseResult.isValid()) {
const data = parseResult.unwrap();
// data.email = 'user@example.com' (lowercased)
// data.age = 25 (number)
}
🔌 Need Custom Validation?
Create powerful, type-safe custom plugins that integrate seamlessly with Luq's architecture.
Learn Custom PluginsError Handling Patterns
Structured Error Processing
Luq provides detailed error information that you can use to build user-friendly error messages:
const result = validator.validate(invalidData);
// Check if validation passed
if (result.isValid()) {
// Get validated data safely
const validData = result.unwrap();
console.log('Validation passed:', validData);
} else {
// Handle validation errors
const errors = result.errors;
errors.forEach(error => {
console.log(`Field: ${error.path}`);
console.log(`Message: ${error.message}`);
console.log(`Code: ${error.code}`);
});
// Example output:
// Field: user.email
// Message: Invalid email format
// Code: stringEmail
}
// Functional approach with Result type
const processedData = result
.map(data => ({ ...data, processed: true }))
.unwrapOr({ processed: false });
// Use with default value
const safeData = result.unwrapOr(defaultUserData);
Performance Patterns
Validator Reuse
✅ Do: Create validators once and reuse them
// Good: Create once, reuse many times
const userValidator = Builder()
.use(requiredPlugin)
.use(stringEmailPlugin)
.for<User>()
.v('email', b => b.string.required().email())
.build();
// Reuse in multiple places
app.post('/users', (req, res) => {
const result = userValidator.validate(req.body);
// ...
});
app.put('/users/:id', (req, res) => {
const result = userValidator.validate(req.body);
// ...
});
❌ Don't: Create validators repeatedly
// Bad: Creating validators inside request handlers
app.post('/users', (req, res) => {
const validator = Builder() // This creates overhead
.use(requiredPlugin)
.use(stringEmailPlugin)
.for<User>()
.v('email', b => b.string.required().email())
.build();
const result = validator.validate(req.body);
// ...
});
Real-World Examples
E-commerce Product Validation
type Product = {
id: string;
name: string;
price: number;
category: 'electronics' | 'clothing' | 'books';
specifications: Record<string, string>;
inStock: boolean;
tags?: string[];
};
const productValidator = Builder()
.use(requiredPlugin)
.use(stringMinPlugin)
.use(numberMinPlugin)
.use(oneOfPlugin)
.use(optionalPlugin)
.use(arrayMaxLengthPlugin)
.for<Product>()
.v('id', b => b.string.required().min(1))
.v('name', b => b.string.required().min(2))
.v('price', b => b.number.required().min(0))
.v('category', b => b.string.required().oneOf(['electronics', 'clothing', 'books']))
.v('specifications', b => b.object.required())
.v('inStock', b => b.boolean.required())
.v('tags', b => b.array.optional().maxLength(10))
.v('tags[*]', b => b.string.required().min(1))
.build();
User Registration with Complex Rules
type UserRegistration = {
username: string;
email: string;
password: string;
confirmPassword: string;
age: number;
terms: boolean;
marketingOptIn?: boolean;
};
const registrationValidator = Builder()
.use(requiredPlugin)
.use(stringMinPlugin)
.use(stringMaxPlugin)
.use(stringEmailPlugin)
.use(numberMinPlugin)
.use(compareFieldPlugin)
.use(optionalPlugin)
.for<UserRegistration>()
.v('username', b =>
b.string
.required()
.min(3)
.max(20)
.pattern(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers and underscore allowed')
)
.v('email', b => b.string.required().email())
.v('password', b =>
b.string
.required()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Must contain uppercase, lowercase and number')
)
.v('confirmPassword', b =>
b.string
.required()
.compareField('password', 'Passwords must match')
)
.v('age', b => b.number.required().min(13))
.v('terms', b =>
b.boolean
.required()
.equals(true, 'You must accept the terms and conditions')
)
.v('marketingOptIn', b => b.boolean.optional())
.build();