fortify-schema
Version:
TypeScript interface-like schema validation system that's easier to use than Zod
396 lines (317 loc) • 9.14 kB
Markdown
# FortifyJS Schema System
A revolutionary schema validation system with **TypeScript interface-like syntax** that's incredibly easy to use and completely separate from validators.
## 🎯 The Problem with Current Schema Libraries
**Zod, Joi, Yup** - they all suffer from the same issues:
- **Complex syntax** that doesn't feel natural
- **Verbose definitions** that are hard to read
- **Steep learning curves** for simple validation
- **Heavy bundle sizes** with unnecessary features
## 💡 Our Solution: Interface-Based Schemas
We created a schema system that **looks and feels exactly like TypeScript interfaces** but provides runtime validation:
```typescript
// ❌ Zod (complex and verbose)
const UserSchema = z.object({
id: z.number().int().positive(),
email: z.string().email(),
name: z.string().min(2).max(50),
age: z.number().int().min(0).max(120).optional(),
isActive: z.boolean().default(true),
tags: z.array(z.string()).max(10).optional(),
});
// ✅ FortifyJS Interface (clean and intuitive)
const UserSchema = Interface({
id: "number",
email: "email",
name: "string",
age: "number?", // Optional with ?
isActive: "boolean?", // Optional with smart defaults
tags: "string[]?", // Optional array
role: "=admin", // Constant values
});
```
**It's literally that simple!** 🎉
## 🚀 Two Powerful Approaches
### 1. Interface-Based (Recommended)
**TypeScript interface syntax with runtime validation**
```typescript
import { Interface } from "fortify-schema";
const UserSchema = Interface({
id: "number",
email: "email",
name: "string",
age: "number?", // Optional
isActive: "boolean?", // Optional
tags: "string[]?", // Optional array
role: "=admin", // Constant value
profile: {
// Nested object
bio: "string?",
avatar: "url?",
},
});
```
### 2. Fluent API (Traditional)
**Chainable method syntax for complex validation**
```typescript
import { Schema } from "fortify-schema";
const UserSchema = Schema.object({
id: Schema.number().int().positive(),
email: Schema.string().email(),
name: Schema.string().min(2).max(50),
age: Schema.number().int().min(0).max(120).optional(),
isActive: Schema.boolean().default(true),
});
```
## 🎯 Why Interface-Based is Better
### ✅ Pros of Interface Syntax:
- **Instantly familiar** to TypeScript developers
- **Extremely readable** - looks like documentation
- **Minimal learning curve** - no new API to learn
- **Faster to write** - less typing, more intuitive
- **Self-documenting** - schema IS the documentation
### ❌ Cons of Traditional Fluent APIs:
- **Verbose and complex** - lots of chaining
- **Hard to read** - especially with nested objects
- **Learning curve** - need to memorize API methods
- **More typing** - repetitive method calls
## 📚 Interface Field Types
### Basic Types
```typescript
{
name: "string", // String
age: "number", // Number
active: "boolean", // Boolean
created: "date", // Date
data: "any" // Any type
}
```
### Optional Fields (with `?`)
```typescript
{
name: "string", // Required
age: "number?", // Optional
bio: "string?", // Optional
tags: "string[]?" // Optional array
}
```
### Format Validation
```typescript
{
email: "email", // Email format
website: "url", // URL format
id: "uuid", // UUID format
phone: "phone", // Phone format
slug: "slug", // URL slug
username: "username" // Username format
}
```
### Number Types
```typescript
{
id: "int", // Integer
count: "positive", // Positive number
price: "float", // Float number
rating: "number" // Any number
}
```
### Array Types
```typescript
{
tags: "string[]", // Array of strings
scores: "number[]", // Array of numbers
flags: "boolean[]", // Array of booleans
ids: "int[]", // Array of integers
emails: "email[]", // Array of emails
urls: "url[]" // Array of URLs
}
```
### Constant Values (Safe Syntax)
```typescript
import { Make } from "fortify-schema";
{
version: Make.const("1.0"), // Safe constant string
status: Make.const(200), // Safe constant number
type: Make.const("user"), // Safe constant value
enabled: Make.const(true) // Safe constant boolean
}
```
### Union Types (Multiple Allowed Values)
```typescript
{
status: Make.union("pending", "accepted", "rejected"),
priority: Make.union("low", "medium", "high"),
role: Make.unionOptional("user", "admin", "moderator") // Optional union
}
```
### Nested Objects
```typescript
{
user: { // Nested object
name: "string",
email: "email"
},
address: { // Deeply nested
street: "string",
city: "string",
coordinates: {
lat: "number",
lng: "number"
}
}
}
```
## 🔥 Real-World Examples
### User Registration
```typescript
const UserRegistrationSchema = Interface({
// Required fields
email: "email",
password: "string",
firstName: "string",
lastName: "string",
// Optional fields
age: "number?",
phone: "phone?",
website: "url?",
// Arrays
interests: "string[]?",
// Nested object
address: {
street: "string",
city: "string",
zipCode: "string",
country: "string",
},
// Safe constants
type: Make.const("user"),
version: Make.const("1.0"),
});
```
### API Response
```typescript
const APIResponseSchema = Interface({
success: "boolean",
status: Make.const(200), // Safe constant status
data: {
users: [
{
// Array of objects
id: "int",
email: "email",
profile: {
name: "string",
avatar: "url?",
},
},
],
pagination: {
page: "int",
total: "int",
hasNext: "boolean",
},
},
errors: "string[]?",
timestamp: "date",
});
```
### Configuration
```typescript
const ConfigSchema = Interface({
database: {
host: "string",
port: "int",
name: "string",
ssl: "boolean?",
},
server: {
port: "int",
cors: "boolean?",
},
features: {
auth: "boolean?",
logging: "boolean?",
},
environment: Make.const("production"), // Safe constant
});
```
## 🛠️ Validation Methods
### Parse (Throws on Error)
```typescript
try {
const user = UserSchema.parse(userData);
// user is fully typed and validated
} catch (error) {
console.error("Validation failed:", error.message);
}
```
### Safe Parse (Returns Result)
```typescript
const result = UserSchema.safeParse(userData);
if (result.success) {
console.log("Valid data:", result.data);
} else {
console.log("Errors:", result.errors);
console.log("Warnings:", result.warnings);
}
```
### Strict Mode
```typescript
const StrictSchema = Interface({
id: "number",
name: "string",
}).strict(); // No extra properties allowed
```
## 🛡️ Safer Syntax for Constants and Unions
### The Problem with Raw Values
```typescript
// ❌ DANGEROUS - Looks like a string type but it's actually a constant!
const BadSchema = Interface({
status: "pending", // Is this a string type or constant "pending"?
role: "=admin", // Very confusing and error-prone!
});
```
### The Solution - Make
```typescript
// ✅ SAFE - Crystal clear what's a type vs constant
import { Interface, Make } from "fortify-schema";
const GoodSchema = Interface({
// Clear type definitions
name: "string",
email: "email",
// Clear constant values
status: Make.const("pending"),
role: Make.const("admin"),
// Clear union types (multiple allowed values)
priority: Make.union("low", "medium", "high"),
category: Make.unionOptional("tech", "business", "design"),
});
```
### Benefits of Make
- **🔒 Type Safety**: No confusion between types and constants
- **📖 Readability**: Crystal clear intent in your schemas
- **🐛 Bug Prevention**: Eliminates dangerous ambiguity
- **🔧 IntelliSense**: Better IDE support and autocomplete
## 📊 Comparison
| Feature | FortifyJS Interface | Zod | Joi |
| ------------------ | ------------------- | ----------- | ---------- |
| **Syntax** | TypeScript-like | Complex API | Verbose |
| **Learning Curve** | None | Steep | Moderate |
| **Readability** | Excellent | Poor | Fair |
| **Bundle Size** | Small | Large | Very Large |
| **Type Safety** | Full | Full | Limited |
| **Performance** | Fast | Slow | Slower |
## 🎉 Get Started
```bash
npm install fortify-schema
```
```typescript
import { Interface } from "fortify-schema";
const MySchema = Interface({
id: "number",
name: "string",
email: "email",
active: "boolean?",
});
const result = MySchema.safeParse(data);
```
**That's it!** No complex APIs to learn, no verbose syntax - just clean, readable schemas that work exactly like TypeScript interfaces! 🚀