@sio-group/form-validation
v0.1.3
Published
[](https://opensource.org/licenses/ISC)  
Downloads
195
Readme
@sio-group/form-validation
A fully typed, composable validation library for validating form inputs and data structures in TypeScript.
This package is designed to work seamlessly with @sio-group/form-builder and @sio-group/form-react, but can be used independently with any validation needs.
Validating user input is crucial, but writing the same validation logic repeatedly is tedious and error-prone. While you could write custom validation functions for every field, ensuring type-safety and consistent error handling is challenging. @sio-group/form-validation provides a set of pre-built, fully typed validation functions that are composable, configurable, and framework-agnostic.
Installation
npm install @sio-group/form-validationQuick Example
import { isRequired, isEmail, isPattern } from '@sio-group/form-validation';
// Create validators
const validateEmail = isEmail();
const validateRequired = isRequired();
// Use them directly
validateEmail('[email protected]'); // null
validateEmail('invalid'); // "Dit is geen geldig e-mailadres."
validateRequired(''); // "Dit is een verplicht veld."
validateRequired('value'); // null
// Compose multiple validations
const validations = [
isRequired('Email is verplicht'),
isEmail('Ongeldig email formaat'),
isPattern(/^[^@]+@company\.com$/, 'Alleen company emails toegestaan')
];
function validateField(value, label) {
return validations
.map(v => v(value, label))
.filter(error => error !== null);
}
console.log(validateField('[email protected]', 'Email'));
// [] (no errors)
console.log(validateField('invalid', 'Email'));
// ['Ongeldig email formaat']Core Concepts
All validations in this library follow the same pattern:
- Higher-Order Functions - Each validator is a function that returns a validation function
- Consistent Return Type - Always returns
nullfor valid values,stringfor errors - Empty Value Handling - Empty values (
null,undefined,'') are considered valid (use with isRequired() for mandatory fields) - Custom Error Messages - Most validators accept an optional custom error message with `{label} placeholder
Available Validations
Core Validations
| Function | Description | Example |
|-------------------|--------------------------------------------|-----------------------------------------------------|
| isRequired() | Checks if value is present (not empty) | isRequired()('') → "Dit is een verplicht veld." |
| isEmail() | Validates email format | isEmail()('[email protected]') → null |
| isUrl() | Validates URL with configurable options | isUrl()('https://example.com') → null |
| isPattern() | Validates against regex pattern | isPattern(/^[A-Z]+$/)('ABC') → null |
Number Validations
| Function | Description | Example |
|----------------------|-------------------------------|---------------------------------|
| isBiggerThan(min) | Checks if number >= minimum | isBiggerThan(18)(20) → null |
| isSmallerThan(max) | Checks if number <= maximum | isSmallerThan(100)(50) → null |
Date Validations
| Function | Description | Example |
|---------------------------|----------------------------|------------------------------------------------------------------|
| dateIsBiggerThan(min) | Checks if date >= minimum | dateIsBiggerThan(new Date('2024-01-01'))('2024-06-01') → null |
| dateIsSmallerThan(max) | Checks if date <= maximum | dateIsSmallerThan(new Date('2024-12-31'))('2024-06-01') → null |
File Validations
| Function | Description Example |
|---------------------------------|-------------------------------|
| isValidFile(maxSize, accept) | Validates file size and type |isValidFile(1024, 'image/*')(file) → null|
Detailed Usage
isRequired
import { isRequired } from '@sio-group/form-validation';
const validate = isRequired('{label} moet ingevuld worden');
validate('test'); // null
validate(''); // "Dit moet ingevuld worden"
validate([]); // "Dit moet ingevuld worden"
validate(false); // "Dit moet ingevuld worden" (for checkboxes)
validate(0); // null (0 is considered valid)isEmail
import { isEmail } from '@sio-group/form-validation';
const validate = isEmail('Ongeldig email adres');
validate('[email protected]'); // null
validate('[email protected]'); // null
validate('invalid'); // "Ongeldig email adres"
validate('user@'); // "Ongeldig email adres"isUrl with Options
import { isUrl } from '@sio-group/form-validation';
// Strict mode (default)
const validateStrict = isUrl();
validateStrict('https://example.com'); // null
validateStrict('http://example.com'); // "Dit moet https gebruiken."
// Development mode (allow localhost and HTTP)
const validateDev = isUrl(true, false, false);
validateDev('http://localhost:3000'); // nullisPattern
import { isPattern } from '@sio-group/form-validation';
// With RegExp (letters and numbers)
const validateCode = isPattern(/^[A-Z0-9]+$/);
validateCode('ABC123'); // null
validateCode('abc123'); // "Dit heeft een ongeldig patroon."
// With string pattern (postcode)
const validatePostcode = isPattern('^[1-9][0-9]{3}[A-Z]{2}$');
validatePostcode('1234AB'); // null
validatePostcode('1234'); // "Dit heeft een ongeldig patroon."
// With flags (case-insensitive)
const validateCase = isPattern('/^[a-z0-9]+$/i');
validateCase('ABC123'); // null (case-insensitive)Number Validations
import { isBiggerThan, isSmallerThan } from '@sio-group/form-validation';
const validateAge = isBiggerThan(18, 'Je moet minimaal 18 jaar zijn');
validateAge(25); // null
validateAge(16); // "Je moet minimaal 18 jaar zijn"
const validatePrice = isSmallerThan(1000, 'Prijs mag maximaal €1000 zijn');
validatePrice(500); // null
validatePrice(1500); // "Prijs mag maximaal €1000 zijn"Date Validations
import { dateIsBiggerThan, dateIsSmallerThan } from '@sio-group/form-validation';
const validateStart = dateIsBiggerThan(
new Date('2024-01-01'),
'Startdatum moet na 1 januari 2024 zijn'
);
validateStart('2024-06-01'); // null
validateStart('2023-12-31'); // "Startdatum moet na 1 januari 2024 zijn"
const validateTime = dateIsBiggerThan(new Date().setTime('12:00'));
validateTime('13:00'); // null
validateTime('11:00'); // "Dit mag niet voor 12:00 zijn"
const validateMeeting = dateIsBiggerThan(new Date('2024-01-01 12:00'));
validateTime('2024-06-01T13:00'); // null
validateTime('2023-12-31T11:00'); // "Dit mag niet voor 1-1-2024, 12:00 zijn"File Validation
import { isValidFile } from '@sio-group/form-validation';
// Single file
const validateFile = isValidFile(1024, 'image/jpeg,image/png'); // max 1MB
const file = new File([''], 'photo.jpg', { type: 'image/jpeg' });
validateFile(file); // null
// Too large
const largeFile = new File(['x'.repeat(2 * 1024 * 1024)], 'large.jpg');
validateFile(largeFile); // "large.jpg is te groot (max 1024 KB)."Integration with @sio-group/form-builder
import { formBuilder } from '@sio-group/form-builder';
import { isRequired, isEmail, isPattern } from '@sio-group/form-validation';
const form = formBuilder()
.addText('username', {
label: 'Username',
required: true,
validations: [
isRequired(),
isPattern(/^[a-z0-9_]{3,20}$/, 'Alleen kleine letters, cijfers en underscores, 3-20 karakters')
]
})
.addEmail('email', {
label: 'Email',
required: true,
validations: [
isRequired(),
isEmail('Ongeldig email formaat')
]
})
.getFields();Type Safety
The library is fully typed and enforces:
- Correct value types per validation function
- Proper configuration options per field type
- Type-safe error messages with placeholder support
import { isBiggerThan, ValidationRule } from '@sio-group/form-validation';
// ✅ Correct: number expected
const validateAge = isBiggerThan(18);
validateAge(25); // null
validateAge('25'); // null (string is cast to number)
// ❌ Type error: boolean not allowed
const validateAge = isBiggerThan(true);
// Type-safe validation arrays
const validations: ValidationRule[] = [
isRequired(),
isEmail(),
isPattern(/^[A-Z]+$/)
];Error Message Placeholders
Most validators accept an optional custom error message with {label} as a placeholder:
const validate = isRequired('{label} is verplicht');
validate('', 'Gebruikersnaam'); // "Gebruikersnaam is verplicht"
const validateMin = isBiggerThan(18, '{label} moet minimaal {min} zijn');
validateMin(16, 'Leeftijd'); // "Leeftijd moet minimaal 18 zijn"Validators without custom message support
The following validators currently use predefined error messages:
import { isUrl, isValidFile } from '@sio-group/validation';
// isUrl uses predefined messages based on configuration
const validateUrl = isUrl(false, false, true);
validateUrl('http://example.com'); // "Dit moet https gebruiken."
validateUrl('localhost'); // "Dit mag geen localhost zijn."
// isValidFile uses predefined messages
const validateFile = isValidFile(1024, 'image/*');
validateFile(largeFile); // "large.jpg is te groot (max 1024 KB)."
validateFile(wrongFile); // "document.pdf is niet toegelaten."Architecture
Each validation function follows the same pattern:
// Higher-order function
export const isEmail = (errorMessage?: string) =>
// Validation function
(val: unknown, label: string = 'Dit'): string | null => {
// Validation logic
return isValid ? null : (errorMessage || `${label} is geen geldig e-mailadres.`);
};This makes the library:
- UI framework agnostic
- Easily composable
- Tree-shakeable
- Fully testable
Ecosystem
@sio-group/form-validation is part of the SIO Form ecosystem:
- @sio-group/form-types - Shared type definitions
- @sio-group/form-builder - Define your form structure
- @sio-group/form-validation - This package: Validate your data (you are here)
- @sio-group/form-react - React renderer and hooks for the builder
This modular approach lets you use the same validation rules across different frameworks and form builders.
Contributing
Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
License
This project is licensed under the ISC License - see the LICENSE file for details.
