Validit Usage Guide
Complete guide to installing and using Validit in your projects.
💡 API Reference
This guide covers API usage and basic patterns. For complete application examples, see Examples.
Table of Contents
Installation
pnpm add @vielzeug/validitnpm install @vielzeug/validityarn add @vielzeug/validitImport
import { v, type Infer } from '@vielzeug/validit';
// Optional: Import types
import type { Schema, ValidationError, ParseResult } from '@vielzeug/validit';Basic Usage
Creating Schemas
import { v } from '@vielzeug/validit';
// Primitive schemas
const stringSchema = v.string();
const numberSchema = v.number();
const booleanSchema = v.boolean();
// With constraints
const emailSchema = v.string().email();
const ageSchema = v.number().int().min(18);
// Complex schemas
const userSchema = v.object({
name: v.string().min(1),
email: v.email(),
age: v.number().int().min(18),
});Validating Data
// Parse – throws on error
const data = schema.parse(input);
// Safe parse – returns result
const result = schema.safeParse(input);
if (result.success) {
console.log(result.data);
} else {
console.log(result.error.issues);
}
// Async validation
const data = await schema.parseAsync(input);
const result = await schema.safeParseAsync(input);Primitive Schemas
String
// Basic string
v.string();
// With validation
v.string()
.min(3) // Min length
.max(100) // Max length
.length(10) // Exact length
.pattern(/^[a-z]+$/) // Regex pattern
.email() // Email format
.url() // URL format
.trim(); // Must be trimmed
// Convenience helpers
v.email(); // Shorthand for v.string().email()
v.url(); // Shorthand for v.string().url()
v.uuid(); // UUID validationNumber
// Basic number
v.number();
// With validation
v.number()
.min(0) // Minimum value
.max(100) // Maximum value
.int() // Must be integer
.positive() // Must be > 0
.negative(); // Must be < 0
// Convenience helpers
v.int().positive(); // Shorthand for v.number().int().positive()
v.int().negative(); // Shorthand for v.number().int().negative()Boolean
v.boolean(); // true or falseDate
v.date() // Date object
.min(new Date('2020-01-01')) // After date
.max(new Date()); // Before dateLiteral
v.literal('active'); // Exactly 'active'
v.literal(42); // Exactly 42
v.literal(true); // Exactly trueEnum
v.enum('red', 'green', 'blue'); // One of these values
v.enum(1, 2, 3); // One of these numbers
v.enum('admin', 'user', 'guest'); // Union of literalsComplex Schemas
Arrays
// Array of strings
v.array(v.string());
// With constraints
v.array(v.string())
.min(1) // Rejects null, undefined, and empty arrays
.max(10) // At most 10 items
.length(5); // Exactly 5 items
// Nested arrays
v.array(v.array(v.number())); // number[][]Objects
// Basic object
v.object({
name: v.string(),
age: v.number(),
});
// Nested objects
v.object({
user: v.object({
name: v.string(),
email: v.email(),
}),
settings: v.object({
notifications: v.boolean(),
}),
});
// Object methods
schema.partial(); // Make all fields optional
schema.pick('name', 'email'); // Select specific fields
schema.omit('password'); // Exclude specific fieldsUnion
// Union of primitives
v.union(v.string(), v.number()); // string | number
// Discriminated union
v.union(
v.object({
type: v.literal('success'),
data: v.string(),
}),
v.object({
type: v.literal('error'),
error: v.string(),
}),
);Validation Methods
parse()
Validates and returns data, throws ValidationError on failure.
try {
const user = userSchema.parse(data);
console.log(user); // Typed data
} catch (error) {
if (error instanceof ValidationError) {
error.issues.forEach((issue) => {
console.log(`${issue.path.join('.')}: ${issue.message}`);
});
}
}safeParse()
Returns a result object, never throws.
const result = schema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed data
} else {
console.log(result.error.issues); // Validation errors
}Async Validation
parseAsync()
For schemas with async validators.
const schema = v
.string()
.email()
.refineAsync(async (email) => {
const exists = await checkDatabase(email);
return !exists;
}, 'Email already exists');
try {
const email = await schema.parseAsync('user@example.com');
} catch (error) {
console.error(error.issues);
}safeParseAsync()
Async version of safeParse().
const result = await schema.safeParseAsync(data);
if (result.success) {
console.log(result.data);
} else {
console.log(result.error.issues);
}Parallel Array Validation
Process array items concurrently for better performance.
const schema = v.array(
v
.object({
id: v.number(),
name: v.string(),
})
.refineAsync(async (item) => {
return await validateItem(item);
}),
);
const items = await schema.parseAsync(largeArray);Modifiers
optional()
Makes a schema accept undefined.
v.string().optional(); // string | undefined
v.email().optional(); // string | undefined
v.object({
name: v.string(),
email: v.string().optional(), // Optional field
});Default Validation Behavior
💡 All Schemas Reject null and undefined by Default
You don't need a required() method! Every schema already validates that values are not null or undefined.
v.string().parse(null); // ❌ Throws "Expected string"
v.number().parse(undefined); // ❌ Throws "Expected number"
v.array(v.string()).parse(null); // ❌ Throws "Expected array"To allow optional values, use .optional():
v.string().optional(); // string | undefined
v.number().optional(); // number | undefinedTo reject empty strings or arrays, use .min(1):
v.string().min(1); // Rejects "" (empty string)
v.string().min(1, 'Name is required'); // Custom error message
v.array(v.string()).min(1); // Rejects [] (empty array)
v.array(v.string()).min(1, 'At least one item required');Example in forms:
v.object({
name: v.string().min(1, 'Name is required'), // Non-empty string
email: v.email(), // Non-empty email
age: v.number().optional(), // Optional number
});nullable()
Makes a schema accept null.
v.string().nullable(); // string | null
v.number().nullable(); // number | nulldefault()
Provides a default value for undefined.
v.string().default('hello'); // Returns 'hello' if undefined
v.number().default(0); // Returns 0 if undefined
v.boolean().default(false); // Returns false if undefined
v.object({
theme: v.enum('light', 'dark').default('light'),
language: v.string().default('en'),
});describe()
Adds a description for better error messages.
v.number().int().min(0).describe('age');
// Errors will show: "age: Must be at least 0"
// Instead of: "value: Must be at least 0"Custom Refinements
refine() – Sync
Add custom validation logic.
// Simple refinement
v.string().refine((val) => val.length >= 3, 'Must be at least 3 characters');
// Multiple refinements
v.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), 'Must contain uppercase')
.refine((val) => /[0-9]/.test(val), 'Must contain number');
// Object-level validation
v.object({
password: v.string(),
confirmPassword: v.string(),
}).refine((data) => data.password === data.confirmPassword, 'Passwords must match');refineAsync() – Async
Add async validation (database checks, API calls).
// Username availability
v.string()
.min(3)
.refineAsync(async (username) => {
const available = await checkAvailability(username);
return available;
}, 'Username already taken');
// Email validation with database
v.email().refineAsync(async (email) => {
const exists = await db.users.findOne({ email });
return !exists;
}, 'Email already registered');
// Combine sync and async
v.string()
.min(3) // Sync
.refine((val) => /^[a-z]+$/.test(val), 'Only lowercase') // Sync
.refineAsync(async (val) => {
// Async
return await isUnique(val);
}, 'Already exists');Error Handling
ValidationError
All validation errors are instances of ValidationError.
import { ValidationError } from '@vielzeug/validit';
try {
schema.parse(data);
} catch (error) {
if (error instanceof ValidationError) {
// Access all issues
error.issues.forEach((issue) => {
console.log(issue.path); // ['user', 'email']
console.log(issue.message); // 'Invalid email'
console.log(issue.code); // 'invalid_email'
console.log(issue.params); // { expected: 'email' }
});
// Formatted message
console.log(error.message);
// "user.email: Invalid email [invalid_email]"
}
}Issue Object
Each issue contains:
type Issue = {
path: (string | number)[]; // Field path
message: string; // Error message
code?: string; // Error code (for i18n)
params?: Record<string, unknown>; // Additional parameters
};Safe Parsing
Use safeParse() to avoid try/catch:
const result = schema.safeParse(data);
if (!result.success) {
// Handle errors
result.error.issues.forEach((issue) => {
showError(issue.path.join('.'), issue.message);
});
} else {
// Use data
console.log(result.data);
}Type Inference
Basic Inference
import { type Infer } from '@vielzeug/validit';
const schema = v.object({
id: v.number(),
name: v.string(),
email: v.email(),
});
type User = Infer<typeof schema>;
// { id: number; name: string; email: string }With Modifiers
const schema = v.object({
id: v.number(),
name: v.string(),
email: v.email().optional(),
age: v.number().nullable(),
role: v.enum('admin', 'user').default('user'),
});
type User = Infer<typeof schema>;
// {
// id: number;
// name: string;
// email?: string | undefined;
// age: number | null;
// role: 'admin' | 'user';
// }Complex Types
const schema = v.object({
user: v.object({
name: v.string(),
contacts: v.array(
v.object({
type: v.enum('email', 'phone'),
value: v.string(),
}),
),
}),
settings: v
.object({
theme: v.enum('light', 'dark'),
})
.optional(),
});
type Data = Infer<typeof schema>;
// {
// user: {
// name: string;
// contacts: Array<{
// type: 'email' | 'phone';
// value: string;
// }>;
// };
// settings?: {
// theme: 'light' | 'dark';
// };
// }Best Practices
✅ Do
- Use convenience schemas (
v.email(),v.int().positive()) - Add custom error messages for better UX
- Use
safeParse()for user input - Leverage type inference with
Infer<typeof schema> - Use
describe()for complex nested schemas
❌ Don't
- Don't use
parse()with async validators (useparseAsync()) - Don't create overly complex nested schemas
- Don't forget to handle validation errors
- Don't use
anytypes – let inference work
Next Steps
💡 Continue Learning
- API Reference – Complete API documentation
- Examples – Practical code examples
- Interactive REPL – Try it in your browser