Skip to content

Partial Validation

Partial validation allows you to validate only specific fields instead of the entire object. This is particularly useful for form updates, API patches, and progressive validation scenarios.

Basic Partial Validation

Validate a single field by passing the field name as the second parameter:

javascript
const userSchema = new Schema({
  name: { 
    type: String,
    rules: { lengthMin: 2 }
  },
  email: { 
    type: String,
    rules: { isEmail: true }
  },
  phone: { 
    type: String,
    required: false 
  }
});

const data = { 
  name: "John",
  email: "invalid-email",
  phone: "555-1234" 
};

// Validate only the name field
const nameResult = userSchema.validate(data, 'name');
// nameResult.ok === true

// Validate only the email field  
const emailResult = userSchema.validate(data, 'email');
// emailResult.ok === false (invalid email format)

Multiple Field Validation

Validate multiple specific fields by passing an array of field names:

javascript
const schema = new Schema({
  firstName: { type: String, rules: { lengthMin: 2 } },
  lastName: { type: String, rules: { lengthMin: 2 } },
  email: { type: String, rules: { isEmail: true } },
  phone: { type: String, required: false },
  address: { type: String, required: false }
});

const data = {
  firstName: "John",
  lastName: "D", // Too short
  email: "john@example.com",
  phone: "555-1234"
};

// Validate only firstName and lastName
const nameResult = schema.validate(data, ['firstName', 'lastName']);
// Will fail because lastName is too short

// Validate only firstName and email
const validResult = schema.validate(data, ['firstName', 'email']);  
// Will pass because both fields are valid

Use Cases for Partial Validation

1. Form Field Validation (Real-time)

Validate individual fields as users type:

javascript
const registrationSchema = new Schema({
  username: {
    type: String,
    rules: {
      lengthMin: 3,
      lengthMax: 20,
      regex: /^[a-zA-Z0-9_]+$/
    }
  },
  email: {
    type: String,
    rules: {
      isEmail: true
    }
  },
  password: {
    type: String,
    rules: {
      lengthMin: 8
    }
  }
});

// Validate username field on blur
const validateUsername = (username) => {
  const result = registrationSchema.validate({ username }, 'username');
  return {
    isValid: result.ok,
    error: result.errors[0] || null
  };
};

// Usage in a form
const usernameCheck = validateUsername("jo"); 
// { isValid: false, error: "Username too short" }

2. API PATCH Operations

Validate only the fields being updated:

javascript
const userUpdateSchema = new Schema({
  name: {
    type: String,
    required: false, // Not required for updates
    rules: { lengthMin: 2 }
  },
  email: {
    type: String,
    required: false,
    rules: { isEmail: true }
  },
  bio: {
    type: String,
    required: false,
    rules: { lengthMax: 500 }
  }
});

// PATCH /users/123 - only updating email
const patchData = {
  email: "newemail@example.com"
};

const result = userUpdateSchema.validate(patchData, 'email');
// Only validates the email field

3. Progressive Form Validation

Validate forms step by step:

javascript
const wizardSchema = new Schema({
  // Step 1: Personal Info
  firstName: { type: String, rules: { lengthMin: 2 } },
  lastName: { type: String, rules: { lengthMin: 2 } },
  
  // Step 2: Contact Info
  email: { type: String, rules: { isEmail: true } },
  phone: { type: String, rules: { regex: /^\d{3}-\d{3}-\d{4}$/ } },
  
  // Step 3: Preferences
  newsletter: { type: Boolean },
  notifications: { type: Array, rules: { enum: ['email', 'sms', 'push'] } }
});

// Validate Step 1
const step1Fields = ['firstName', 'lastName'];
const step1Result = wizardSchema.validate(formData, step1Fields);

// Validate Step 2  
const step2Fields = ['email', 'phone'];
const step2Result = wizardSchema.validate(formData, step2Fields);

4. Conditional Field Validation

Validate fields based on conditions:

javascript
const orderSchema = new Schema({
  paymentMethod: {
    type: String,
    rules: { enum: ['credit_card', 'paypal', 'bank_transfer'] }
  },
  creditCardNumber: {
    type: String,
    required: false,
    rules: { regex: /^\d{4}-\d{4}-\d{4}-\d{4}$/ }
  },
  paypalEmail: {
    type: String,
    required: false,
    rules: { isEmail: true }
  },
  bankAccount: {
    type: String,
    required: false,
    rules: { regex: /^\d{10,12}$/ }
  }
});

// Validate payment-specific fields based on method
const validatePaymentDetails = (data) => {
  const { paymentMethod } = data;
  let fieldsToValidate = ['paymentMethod'];
  
  switch (paymentMethod) {
    case 'credit_card':
      fieldsToValidate.push('creditCardNumber');
      break;
    case 'paypal':
      fieldsToValidate.push('paypalEmail');  
      break;
    case 'bank_transfer':
      fieldsToValidate.push('bankAccount');
      break;
  }
  
  return orderSchema.validate(data, fieldsToValidate);
};

Working with Nested Fields

For nested objects, use dot notation to specify field paths:

javascript
const userSchema = new Schema({
  profile: {
    personalInfo: {
      firstName: { type: String, rules: { lengthMin: 2 } },
      lastName: { type: String, rules: { lengthMin: 2 } }
    },
    contactInfo: {
      email: { type: String, rules: { isEmail: true } },
      phone: { type: String, required: false }
    }
  }
});

const userData = {
  profile: {
    personalInfo: {
      firstName: "John",
      lastName: "Doe"
    },
    contactInfo: {
      email: "john@example.com",
      phone: "555-1234"
    }
  }
};

// Note: Validno currently validates at the top level
// For nested validation, you would validate the parent object
const profileResult = userSchema.validate(userData, 'profile');

Handling Missing Fields

Partial validation handles missing fields gracefully:

javascript
const schema = new Schema({
  name: { type: String, rules: { lengthMin: 2 } },
  email: { type: String, rules: { isEmail: true } },
  age: { type: Number, rules: { min: 18 } }
});

const incompleteData = {
  name: "John"
  // email and age are missing
};

// Validate only the name field (which exists)
const nameResult = schema.validate(incompleteData, 'name');
// Result: valid

// Validate email field (which is missing)  
const emailResult = schema.validate(incompleteData, 'email');
// Result: invalid (field is required but missing)

// Validate multiple fields including missing ones
const multiResult = schema.validate(incompleteData, ['name', 'email']);
// Result: invalid (email is missing)

Optional Fields in Partial Validation

Optional fields behave differently in partial validation:

javascript
const schema = new Schema({
  name: { type: String, required: true },
  nickname: { type: String, required: false },
  bio: { type: String, required: false, rules: { lengthMax: 200 } }
});

const data = { name: "John" };

// Validate optional field that's not provided
const nicknameResult = schema.validate(data, 'nickname');
// Result: valid (optional field can be missing)

// Validate optional field with validation rules
const bioData = { bio: "Very long bio that exceeds 200 characters..." };
const bioResult = schema.validate(bioData, 'bio');
// Result: invalid (fails lengthMax rule even though field is optional)

Best Practices

javascript
// Good: Validate related fields as a group
const passwordFields = ['password', 'confirmPassword'];
const result = schema.validate(data, passwordFields);

// Avoid: Validating dependent fields separately
const passwordResult = schema.validate(data, 'password');
const confirmResult = schema.validate(data, 'confirmPassword');

2. Handle Missing Dependencies

javascript
const validateWithDependencies = (data, fields) => {
  const allFields = [...fields];
  
  // Add dependent fields
  if (fields.includes('confirmPassword') && !fields.includes('password')) {
    allFields.push('password');
  }
  
  return schema.validate(data, allFields);
};

3. Provide Clear Feedback

javascript
const validateField = (data, fieldName) => {
  const result = schema.validate(data, fieldName);
  
  return {
    field: fieldName,
    isValid: result.ok,
    errors: result.errorsByKeys[fieldName] || [],
    message: result.ok ? 'Valid' : result.errors.join(', ')
  };
};

4. Cache Validation Results

javascript
class ValidationCache {
  constructor(schema) {
    this.schema = schema;
    this.cache = new Map();
  }
  
  validateField(data, field) {
    const key = `${field}:${JSON.stringify(data[field])}`;
    
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    
    const result = this.schema.validate(data, field);
    this.cache.set(key, result);
    
    return result;
  }
  
  clearCache() {
    this.cache.clear();
  }
}

TypeScript Support

Partial validation works with TypeScript generics:

typescript
interface User {
  name: string;
  email: string;
  age?: number;
}

const schema = new Schema({
  name: { type: String },
  email: { type: String, rules: { isEmail: true } },
  age: { type: Number, required: false }
});

// Type-safe partial validation
const result = schema.validate<User>(userData, 'email');
const multiResult = schema.validate<User>(userData, ['name', 'email']);

Real-World Examples

Form Validation Hook (React-like)

javascript
const useFieldValidation = (schema, data) => {
  const [errors, setErrors] = useState({});
  
  const validateField = (fieldName) => {
    const result = schema.validate(data, fieldName);
    
    setErrors(prev => ({
      ...prev,
      [fieldName]: result.ok ? null : result.errors[0]
    }));
    
    return result.ok;
  };
  
  const validateFields = (fieldNames) => {
    const result = schema.validate(data, fieldNames);
    
    const newErrors = {};
    fieldNames.forEach(field => {
      newErrors[field] = result.errorsByKeys[field]?.[0] || null;
    });
    
    setErrors(prev => ({ ...prev, ...newErrors }));
    
    return result.ok;
  };
  
  return { validateField, validateFields, errors };
};

API Validation Middleware

javascript
const createValidationMiddleware = (schema) => {
  return (req, res, next) => {
    const method = req.method;
    
    if (method === 'POST') {
      // Validate all required fields for creation
      const result = schema.validate(req.body);
      if (!result.ok) {
        return res.status(400).json({ errors: result.errors });
      }
    } else if (method === 'PATCH') {
      // Validate only provided fields for updates
      const providedFields = Object.keys(req.body);
      const result = schema.validate(req.body, providedFields);
      if (!result.ok) {
        return res.status(400).json({ errors: result.errors });
      }
    }
    
    next();
  };
};

Next Steps

Released under the ISC License.