Schema & Validation Utilities
Describe a value as JSON Schema, carry its TypeScript type along, and check a value against it at runtime.
These are standalone helpers for your server code. No part of the API client calls them: a schema never reaches a request, and a response is never validated against one. Reach for them where you receive data you did not type – a request body, a webhook payload, a configuration file.
Core Concepts
Validators
The foundation of the validation system is the Validator<T> interface, which provides type-safe validation capabilities:
interface Validator<T = unknown> {
/**
* Validates that the structure of a value matches this schema,
* and returns a typed version of the value if it does.
*/
readonly validate?: (value: unknown) => ValidationResult<T>
}Schemas
Schemas extend validators with JSON Schema support:
interface Schema<T = unknown> extends Validator<T> {
/** Schema type for inference. */
_type: T
readonly jsonSchema: JSONSchema7
}Creating Schemas
Create schemas using the jsonSchema utility function:
import { jsonSchema } from 'apiful/utils'
const userSchema = jsonSchema<{
id: number
name: string
}>({
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' }
},
required: ['id', 'name']
})TIP
Always provide the TypeScript type as a generic parameter to ensure type safety between your JSON Schema and TypeScript definitions.
Custom Validation Logic
You can provide custom validation logic alongside the JSON Schema:
const userSchema = jsonSchema<User>(
{
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' }
},
required: ['id', 'name']
},
{
validate: (value) => {
if (myValidationLogic(value)) {
return { success: false, error: new Error('Custom validation failed') }
}
return { success: true, value: value as User }
}
}
)Validation
Unsafe Validation
Use the validateTypes function for scenarios where validation failures should throw errors:
import { validateTypes } from 'apiful/utils'
try {
const user = validateTypes({
value: data,
schema: userSchema
})
// user is typed as { id: number, name: string }
}
catch (error) {
if (error instanceof TypeValidationError) {
console.error('Validation failed:', error.value, error.cause)
}
}Safe Validation
For graceful error handling, use safeValidateTypes to handle validation results without throwing:
import { safeValidateTypes } from 'apiful/utils'
const result = safeValidateTypes({
value: data,
schema: userSchema
})
if (result.success) {
// `result.value` is typed data
console.log(result.value.name)
}
else {
// `TypeValidationError` with details
console.error(result.error.value, result.error.cause)
}NOTE
Safe validation is particularly useful in API endpoints where you want to return validation errors to the client instead of crashing the server.
Custom Validators
Create standalone validators without JSON Schema using the validator function:
import { validator } from 'apiful/utils'
const emailValidator = validator<string>((value) => {
if (typeof value !== 'string' || !value.includes('@'))
return { success: false, error: new Error('Invalid email') }
return { success: true, value }
})TIP
Custom validators are perfect for business logic validation that goes beyond JSON Schema capabilities, such as checking database constraints or complex business rules.
Type Guards
Schema Type Guard
Ensure a value is a valid Schema using the isSchema type guard:
import { isSchema } from 'apiful/utils'
if (isSchema(value)) {
console.log(value.jsonSchema)
}Validator Type Guard
Ensure a value is a valid Validator using the isValidator type guard:
import { isValidator } from 'apiful/utils'
if (isValidator(value)) {
if (value.validate) {
const result = value.validate(someValue)
}
}Error Handling
TypeValidationError
Thrown during validation failures with detailed context:
declare class TypeValidationError extends Error {
readonly value: unknown // The value that failed validation
readonly cause?: unknown // The underlying cause of the failure
}Error Messages
Error messages are automatically formatted to include:
- The invalid value (JSON stringified)
- The cause of the validation failure
- Proper error message extraction from various error types
Best Practices
Type Safety: Always provide explicit types when creating schemas:
tsjsonSchema<YourType>({ /* ... */ })Error Handling: Choose the appropriate validation method:
- Use
validateTypeswhen validation failures should halt execution - Use
safeValidateTypeswhen you need to handle validation errors gracefully
- Use
Custom Validation: Combine JSON Schema with custom validators for complex validation rules
Modularity: Create reusable schemas and validators for common patterns
Type Guards: Use type guards when working with dynamic schemas or validators