Validation Providers
Validation Providers implement the Validator interface from @stratix/core, enabling type-safe schema validation throughout your Stratix application.
Zod Validator
Package: @stratix/validation-zod
Zod-based schema validation with convenient helper functions.
Installation
npm install @stratix/validation-zod
Or using the CLI:
stratix add validation
Features
- ✅ Type-safe validation with Zod schemas
- ✅ Helper functions for common patterns
- ✅ Async validation support
- ✅ Result pattern integration
- ✅ Custom error messages
Quick Start
import { validateOrThrow, z } from '@stratix/validation-zod';
const userSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
age: z.number().int().positive()
});
// Throws ValidationErrorException if invalid
const user = validateOrThrow(userSchema, {
email: 'user@example.com',
name: 'John',
age: 30
});
Helper Functions
validateOrThrow
Validates and throws ValidationErrorException on failure:
import { validateOrThrow, z } from '@stratix/validation-zod';
const schema = z.object({ email: z.string().email() });
try {
const data = validateOrThrow(schema, input);
} catch (error) {
if (error instanceof ValidationErrorException) {
console.error(error.errors);
}
}
validateWithResult
Returns a Result object instead of throwing:
import { validateWithResult, z } from '@stratix/validation-zod';
const schema = z.object({ age: z.number().int() });
const result = validateWithResult(schema, { age: 25 });
if (result.isSuccess) {
console.log('Valid:', result.value);
} else {
console.error('Errors:', result.error);
}
Other Helpers
validateOrThrowAsync- Async version of validateOrThrowvalidateWithResultAsync- Async version of validateWithResultvalidate- Returns booleanvalidateAsync- Async boolean validation
Usage Patterns
In Value Objects
import { ValueObject } from '@stratix/core';
import { validateOrThrow, z } from '@stratix/validation-zod';
export class Email extends ValueObject<string> {
private static schema = z.string().email();
private constructor(value: string) {
super(value);
}
static create(value: string): Email {
const validated = validateOrThrow(this.schema, value);
return new Email(validated);
}
}
In Commands
import { Command } from '@stratix/core';
import { validateOrThrow, z } from '@stratix/validation-zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
password: z.string().min(8)
});
export class CreateUserCommand extends Command {
constructor(
public readonly email: string,
public readonly name: string,
public readonly password: string
) {
super();
validateOrThrow(createUserSchema, { email, name, password });
}
}
In HTTP Routes
import { validateOrThrow, z } from '@stratix/validation-zod';
const createUserBodySchema = z.object({
email: z.string().email(),
name: z.string().min(2)
});
app.post('/users', async (req, res) => {
try {
const body = validateOrThrow(createUserBodySchema, req.body);
const user = await userService.create(body);
res.status(201).json(user);
} catch (error) {
if (error instanceof ValidationErrorException) {
res.status(400).json({ errors: error.errors });
}
}
});
Validator Interface
All validation providers implement the Validator interface:
interface Validator<TSchema> {
validate<T>(schema: TSchema, data: unknown): T;
validateAsync<T>(schema: TSchema, data: unknown): Promise<T>;
}
Why Validation is a Provider
@stratix/validation-zod is classified as a provider (not a library) because:
- External Dependency: Wraps the Zod library
- Interface Implementation: Implements the
Validatorinterface - Swappable: Could be replaced with other validators (Yup, Joi, etc.)
- Not Pure: Depends on external validation logic
This is consistent with other providers like AI providers (wrap OpenAI/Anthropic) and DI providers (wrap Awilix).
Creating Custom Validation Providers
To create your own validation provider:
import type { Validator } from '@stratix/core';
import Joi from 'joi';
export class JoiValidator implements Validator<Joi.Schema> {
validate<T>(schema: Joi.Schema, data: unknown): T {
const result = schema.validate(data);
if (result.error) {
throw new ValidationError(result.error.message);
}
return result.value as T;
}
async validateAsync<T>(schema: Joi.Schema, data: unknown): Promise<T> {
const result = await schema.validateAsync(data);
return result as T;
}
}
Best Practices
1. Define Schemas Once
// schemas/user.schemas.ts
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2)
});
export const updateUserSchema = createUserSchema.partial();
2. Use Type Inference
const userSchema = z.object({
email: z.string().email(),
name: z.string()
});
type User = z.infer<typeof userSchema>;
3. Validate at Boundaries
// ✅ Good: Validate at HTTP boundary
app.post('/users', (req, res) => {
const body = validateOrThrow(schema, req.body);
});
// ❌ Bad: Trust input
app.post('/users', (req, res) => {
const user = await userService.create(req.body);
});
Next Steps
- Error Handling - Map validation failures to
DomainError/RuntimeError - Value Objects - Use validation in VOs
- CQRS - Validate commands and queries