ValidaThor
A lightweight, type-safe validation library for TypeScript with a focus on developer experience and performance.
- Name
- Kevin Østerkilde
- @kosai106
6 min. read

Introduction
As applications grow in complexity, the need for robust data validation becomes increasingly important. Whether it's validating user input, API responses, or configuration files, ensuring that data conforms to expected shapes and constraints is crucial for maintaining application stability and security.
I found myself repeatedly writing validation code across multiple projects, and while there are existing libraries that do validation really well with simple and easy to use APIs, I still wondered if I could make my own and how it would stand up.
Check it out now 👉 validathor.oesterkilde.dk 👈
How
ValidaThor was built with a few core principles in mind:
- Type safety: Validations should be fully type-safe, with TypeScript inferring types from validation schemas.
- Developer experience: The API should be intuitive and easy to use, with helpful error messages.
- Performance: Validation should be efficient, with minimal overhead.
- Extensibility: It should be easy to add custom validators and extend the library.
Core Architecture
At its core, ValidaThor is built around the concept of schemas. A schema defines the expected shape and constraints of data, and can be used to validate that data at runtime. The library provides a set of primitive validators (string
, number
, boolean
, date
, regex
and more) and compositional validators (object
, array
, tuple
and more) that can be combined to create complex validation rules. A key architectural decision is the use of a modifier-based validation system, where validators accept an array of modifiers that can be chained together for flexible validation rules.
The library leverages TypeScript's type system to provide full type safety. When you define a schema, TypeScript can infer the shape of the data that the schema validates, allowing for autocompletion and type checking when using the validated data.
Error Handling
ValidaThor features a sophisticated error handling system built around two distinct error types: ValidationError
for validation constraint failures and TypeError
for type mismatches. The library implements a comprehensive error code system that categorizes errors by type and provides meaningful, contextual error messages. Each error has a unique code (e.g., ERR_TYP_2000
for string type errors; ERR_VAL_2001
for minimum length violations) and can include dynamic parameters in error messages:
// Example of how error codes are used in schemas
export const string = (modifiers: StringSchemaModifiers = [], message?: { type_error?: string }): Parser<string> => ({
name: 'string',
parse: (value: unknown) => {
assert(typeof value === 'string', new TypeError(message?.type_error || ERROR_CODES.ERR_TYP_2000.message()));
// Modifiers might throw ValidationError with specific codes
// e.g., ERR_VAL_2001: "Value must be at least 8 characters long"
validateModifiers(value, modifiers);
return value;
},
});
This systematic approach to error handling makes debugging straightforward and provides users with actionable feedback about what went wrong and how to fix it.
Composition
ValidaThor is designed to be composable, allowing you to build complex validation schemas from simpler ones. This real-world example demonstrates how to compose schemas for a complete user profile system showcasing reusable components, nested objects, optional fields and enumerations:
// Reusable schemas for common data types
const phoneSchema = v.string([v.pattern(/^\+?[\d\s-()]+$/), v.min(10), v.max(20)]);
const addressSchema = v.object({
street: v.string([v.min(5)]),
city: v.string([v.min(2)]),
state: v.string([v.length(2)]),
zip: v.string([v.pattern(/^\d{5}(-\d{4})?$/)]),
country: v.enum_(['US', 'CA', 'MX']),
});
// Compose schemas for a user profile
const userProfileSchema = v.object({
id: v.string([v.pattern(/^usr_[a-zA-Z0-9]{12}$/)]),
personalInfo: v.object({
firstName: v.string([v.min(2), v.max(50)]),
lastName: v.string([v.min(2), v.max(50)]),
dateOfBirth: v.date([v.max(new Date())]),
phone: phoneSchema, // Reusing the phone schema
}),
email: v.string([v.email()]),
addresses: v.object({
billing: addressSchema,
shipping: v.optional(addressSchema), // Optional shipping address
}),
preferences: v.object({
notifications: v.array([v.enum_(['email', 'sms', 'push'])]),
theme: v.enum_(['light', 'dark', 'auto']),
}),
});
This composability makes it easy to reuse validation logic across your application, reducing duplication and making your code more maintainable.
Performance Optimizations
One of the challenges with validation libraries is balancing expressiveness with performance. ValidaThor achieves excellent runtime performance and minimal overhead through several key optimizations:
Core Optimizations
- Lazy evaluation: Validators are only executed when needed, reducing the overhead of validation. Modifiers are stored as arrays and only processed during parsing, not during schema creation.
- Early termination: It stops as soon as an error is encountered, avoiding unnecessary work. This is particularly beneficial for complex nested objects where a single invalid field can fail the entire validation early.
- Optimized primitives: Core validators use native JavaScript type checks (
typeof
) before any complex validation logic, ensuring the fastest possible path for type validation.
Outcome
ValidaThor has been a valuable addition to my toolkit, providing a robust solution for data validation across my projects. Its type-safe approach and developer-friendly API have made it easy to integrate into existing codebases, and its performance optimizations have ensured that it doesn't become a bottleneck in my applications.
Key Benefits
- Type safety: ValidaThor leverages TypeScript's type system to provide full type safety, making it easy to catch validation errors at compile time.
- Developer experience: The intuitive API and helpful error messages make ValidaThor easy to use and debug.
- Performance: The library's performance optimizations ensure that validation is fast and efficient, even for complex schemas.
- Extensibility: Support for custom validators makes it easy to adapt ValidaThor to specific use cases.
Challenges
Building ValidaThor wasn't without its challenges:
- Type inference: Getting TypeScript to correctly infer types from validation schemas required careful design and implementation.
- Error reporting: Balancing detailed error reporting with performance was a delicate balance.
- Edge cases: Handling edge cases like
null
,undefined
, and circular references required careful consideration.
Despite these challenges, the end result is a validation library that meets my needs and provides a solid foundation for data validation in my projects.
Future Improvements
There are several areas where ValidaThor could be improved in the future:
- Schema transformation: Adding support for transforming data during validation, similar to Zod's transform API.
- Recursive schemas: Improving support for recursive schemas, such as trees or linked lists.
- JIT compilation: Implementing JIT compilation of validation schemas to create optimized validation functions for better performance in hot paths.
- Documentation: Enhancing the documentation with more examples and best practices.
- Tools: Developing tools to generate schemas from TypeScript interfaces and JSON examples.
I'm actively working on these improvements and welcome contributions from the community to make ValidaThor even better.
Conclusion
ValidaThor has been a valuable project for me, providing a robust solution for data validation in my TypeScript projects. Its focus on type safety, developer experience, and performance makes it a compelling choice for anyone looking for a validation library that integrates well with TypeScript.
If you're interested in learning more about ValidaThor or contributing to the project, check out the GitHub repository or try it out in your own projects. Whether you're validating user input, API responses, or configuration files, ValidaThor provides a solid foundation for ensuring that your data is valid and type-safe.