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 Plugins

Error 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();