@pfeiferio/validator
v0.3.1
Published
TypeScript-first schema validation for APIs and forms with explicit parameters and async support
Maintainers
Readme
@pfeiferio/validator
A small, TypeScript-first validation framework for APIs and forms.
It focuses on explicit parameters, schema-based validation, and full control over validation logic — including synchronous and asynchronous rules, nested objects, value transformation, and precise error paths.
If you prefer clarity over magic and want validation and transformation in one place, this library is for you.
Why this library exists
Most validation libraries optimize for brevity or implicit inference. @pfeiferio/validator optimizes for control and
predictability.
- No decorators
- No hidden schema magic
- No JSON Schema indirection
Instead, you explicitly define:
- which parameters exist
- when they are required
- how they are validated
- how values are transformed
This makes it especially well suited for backend APIs and complex request validation.
Installation
npm install @pfeiferio/validatorQuick Example
import {Schema, createParameter} from '@pfeiferio/validator'
import {checkString, checkNumber} from '@pfeiferio/check-primitives'
const name = createParameter('name')
.validation(checkString)
const age = createParameter('age')
.validation(checkNumber)
const schema = new Schema()
.add(name)
.add(age)
const result = schema.validate({name: 'Alice', age: 30})
if (result.errors.hasErrors()) {
console.log(result.errors.errors)
} else {
console.log(name.value, age.value)
}Real-world Examples
Real-world usage examples can be found in the examples/ folder.
The examples demonstrate framework integrations, reusable parameter definitions and execution-node based validation logic.
Core Concepts
Parameters
A parameter represents a single input value.
Parameters:
- can be required or optional
- validate and transform values
- can be synchronous or asynchronous
- support arrays and nested objects
- expose their sanitized value after validation
const email = createParameter('email')
.validation(value => value.toLowerCase())Async validation:
const username = createParameter('username')
.asyncValidation(async value => {
await checkAvailability(value)
return value
})Schema
A schema groups parameters and controls validation execution.
const schema = new Schema()
.add(param1)
.add(param2)Validation:
const result = schema.validate(data)data can be:
- a plain object
- a
SearchStore(advanced use)
Schemas automatically handle sync and async parameters. If async validation is involved, validate() returns a Promise.
Advanced Examples
Working with Arrays and Nested Objects
You can validate arrays of objects and access individual nodes in the execution tree:
import {createParameter, Schema} from '@pfeiferio/validator'
const age = createParameter('age').noValidation()
const name = createParameter('name').noValidation()
const user = createParameter('user')
.object({age, name})
.many()
const schema = new Schema()
const result = await schema.add(user).validate({
user: [
{age: 18, name: 'max'},
{age: 20, name: 'ben'},
{age: 22, name: 'johnny'}
]
})
// Access all user nodes
result.nodes(user).map(node => node.value)
// → [{ age: 18, name: 'max' }, { age: 20, name: 'ben' }, { age: 22, name: 'johnny' }]
// Access all age values
result.nodes(age).map(node => node.value)
// → [18, 20, 22]
// Access first age
result.nodes(age).at(0)?.value
// → 18
// Access last age
result.nodes(age).at(-1)?.value
// → 22Conditional Requirements with requiredIf
You can define dynamic requirements based on other field values using the execution tree:
import {createParameter, Schema} from '@pfeiferio/validator'
const age = createParameter('age').noValidation()
const name = createParameter('name').noValidation()
const parentName = createParameter('parentName', false)
.requiredIf((sanitizedValues, node, requiredIfCtx) => {
const ageNode = node.siblings(age).at(0)
if (!ageNode || ageNode.value >= 18) {
return false
}
requiredIfCtx
.dependsOn(ageNode)
.reason('age.min.18')
return true
})
.noValidation()
const user = createParameter('user')
.object({age, name, parentName})
.many()
const schema = new Schema()
const result = await schema.add(user).validate({
user: [
{age: 37, name: 'max'},
{age: 14, name: 'ben'}, // Missing parentName → ERROR
{age: 22, name: 'johnny'}
]
})
console.log(result.errors.errors)
/**
* [
* {
* path: 'user.1.parentName',
* name: 'parentName',
* reason: 'required.if',
* context: {
* dependsOn: ['user.1.age'],
* reasons: ['age.min.18']
* }
* }
* ]
*/SearchStore (advanced)
SearchStore is a thin abstraction over input data with helper methods like get() and has().
import {SearchStore} from '@pfeiferio/validator'
const store = new SearchStore(req.body)
schema.validate(store)Using plain data is recommended unless you need advanced control.
Error Handling
Validation errors include precise paths, making them easy to map back to APIs or UIs.
{
path: 'user.email',
name
:
'email',
reason
:
'email.invalidFormat'
}For conditional requirements, errors include context about dependencies:
{
path: 'user.1.parentName',
name
:
'parentName',
reason
:
'required.if',
context
:
{
dependsOn: ['user.1.age'],
reasons
:
['age.min.18']
}
}Sync vs Async
Parameters can be synchronous or asynchronous.
The schema automatically detects async usage and behaves accordingly.
Helper guards are available:
import {
isParameter,
isParameterSync,
isParameterAsync
} from '@pfeiferio/validator'When to use this library
- You want full control over validation logic
- You need async validation (e.g. database checks)
- You validate complex, nested request payloads
- You want validation and transformation in one step
- You need access to the validation execution tree (for cross-field validation)
When not to use it
- You only need simple form validation
- You want automatic schema inference
- You prefer JSON Schema–based tooling
Documentation
Advanced examples and internal concepts can be found in:
/docsRelated Packages
@pfeiferio/check-primitives– primitive validation helpers
License
MIT
