Fortify Schema
TypeScript-First Validation Library with Intuitive Syntax
Report any bugs to Nehonix-Team via: team@nehonix.space
Fortify Schema, is a powerful TypeScript-first schema validation library that combines the familiarity of TypeScript interfaces with runtime validation and automatic type inference.
β¨ Key Features
- π― Interface-like Syntax β Define schemas using familiar TypeScript interface syntax
- β‘ Runtime Type Inference β Validated data is automatically typed without manual casting
- π« Non-Empty Validation β New "!" syntax for non-empty strings and non-zero numbers
- π§ Rich Constraints β Support for string length, number ranges, arrays, unions, and constants
-
π οΈ Schema Utilities β Transform schemas with
partial()
,omit()
, andextend()
methods - π¨ VSCode Integration β Dedicated extension with syntax highlighting and IntelliSense
- π¦ Zero Dependencies β Lightweight and performant
π Quick Start
Installation
npm install fortify-schema
# or
yarn add fortify-schema
# or
pnpm add fortify-schema
Requirements: TypeScript 4.5+ and Node.js 14+
Basic Usage
import { Interface } from 'fortify-schema';
// Define your schema
const UserSchema = Interface({
id: "number!", // Non-zero number (basic type with "!")
name: "string(2,50)", // String with length constraints (2-50 chars)
email: "email", // Email validation (no "!" available)
age: "number(18,120)?", // Optional age between 18-120
bio: "string!?", // Optional, but if provided must be non-empty
tags: "string[](1,10)?", // Optional array of 1-10 strings
status: "active|inactive", // Union type
role: "=admin", // Constant value
});
// Valid data
const userData = {
id: 1, // β
Non-zero number
name: "Jane Doe", // β
String within length constraints
email: "hello@example.com", // β
Valid email format
status: "active",
role: "admin",
};
// Invalid data examples
const invalidData = {
id: 0, // β Fails: number! rejects 0
name: "J", // β Fails: string(2,50) requires minimum 2 chars
email: "invalid-email", // β Fails: invalid email format
status: "active",
role: "admin",
};
const result = UserSchema.safeParse(userData);
if (result.success) {
// β
Data is valid and properly typed
console.log("Welcome,", result.data.name);
console.log("User ID:", result.data.id); // TypeScript knows this is a number
} else {
// β Handle validation errors
console.error("Validation failed:", result.error.issues);
}
π Schema Syntax Reference
Primitive Types
const schema = Interface({
text: "string", // Any string (including empty "")
count: "number", // Any number (including 0)
flag: "boolean", // Boolean value
timestamp: "date", // Date object
contact: "email", // Valid email format
website: "url", // Valid URL format
});
Non-Empty/Non-Zero Values (New!)
const schema = Interface({
// "!" syntax - ONLY for basic string and number types
name: "string!", // Non-empty string (rejects "")
count: "number!", // Non-zero number (rejects 0)
// Other types don't support "!" - they have their own validation logic
email: "email", // β Cannot do "email!" - not supported
url: "url", // β Cannot do "url!" - not supported
date: "date", // β Cannot do "date!" - not supported
// Optional variants
title: "string!?", // Optional, but if provided must be non-empty
score: "number!?", // Optional, but if provided must be non-zero
});
Constrained Types
const schema = Interface({
// Constraint syntax - for length/range validation
username: "string(3,20)", // String with length 3-20
age: "number(0,150)", // Number between 0-150
score: "number(0,)", // Number >= 0
code: "string(,10)", // String with max length 10
});
// Non-empty validation - alternative to constraints
const nonEmptySchema = Interface({
title: "string!", // Non-empty string (simpler than "string(1,)")
quantity: "number!", // Non-zero number
});
// IMPORTANT: Choose ONE approach - cannot combine both:
// β
"string(1,100)" - Use constraints for length validation
// β
"string!" - Use for simple non-empty validation
// β "string!(1,100)" - INVALID: Cannot combine both syntaxes
Arrays
const schema = Interface({
tags: "string[]", // Array of strings
scores: "number[](1,5)", // 1-5 numbers
items: "string[](,10)", // Max 10 strings
required: "number[](1,)", // At least 1 number
});
Unions and Constants
const schema = Interface({
status: "pending|approved|rejected", // Union type
role: "=admin", // Constant value
priority: "low|medium|high", // Multiple options
version: "=1.0.0", // Exact match
});
Optional Fields
const schema = Interface({
id: "number!", // Required non-zero number
name: "string!", // Required non-empty string
email: "email!?", // Optional, but if provided must be non-empty
phone: "string?", // Optional, can be empty string
bio: "string!?", // Optional, but if provided must be non-empty
tags: "string[]?", // Optional array
});
π§ Schema Utilities
Making Fields Optional
const BaseSchema = Interface({
id: "number",
name: "string",
email: "email",
});
// Make specific fields optional
const PartialSchema = Mod.partial(BaseSchema, ['email']);
// Result: { id: number, name: string, email?: string }
// Make all fields optional
const AllOptionalSchema = Mod.partial(BaseSchema);
// Result: { id?: number, name?: string, email?: string }
Omitting Fields
const UserSchema = Interface({
id: "number",
name: "string",
email: "email",
password: "string",
});
// Remove sensitive fields
const PublicUserSchema = Mod.omit(UserSchema, ['password']);
// Result: { id: number, name: string, email: string }
Extending Schemas
const BaseSchema = Interface({
id: "number",
name: "string",
});
// Add new fields
const ExtendedSchema = Mod.extend(BaseSchema, {
email: "email",
createdAt: "date",
});
// Result: { id: number, name: string, email: string, createdAt: Date }
π Advanced Examples
API Response Validation
const ApiResponseSchema = Interface({
success: "boolean",
data: Interface({
users: Interface({
id: "number",
username: "string(3,20)",
email: "email",
role: "admin|user|moderator",
isActive: "boolean",
lastLogin: "date?",
})[],
}),
pagination: Interface({
page: "number(1,)",
limit: "number(1,100)",
total: "number(0,)",
}),
});
// Use in API handler
async function getUsers(req: Request) {
const response = await fetch('/api/users');
const rawData = await response.json();
const result = ApiResponseSchema.safeParse(rawData);
if (!result.success) {
throw new Error('Invalid API response format');
}
// Fully typed response data
return result.data;
}
Form Validation
const ContactFormSchema = Interface({
name: "string!", // Required non-empty name (basic validation)
email: "email", // Required valid email (has built-in validation)
phone: "string(10,15)?", // Optional phone, if provided 10-15 chars
subject: "support|sales|general",
message: "string(10,1000)", // Required message, 10-1000 chars (use constraints)
newsletter: "boolean?",
});
// Test data
const formData = {
name: "", // β Will fail: string! rejects empty
email: "user@example.com", // β
Valid email
subject: "support", // β
Valid union value
message: "Hi there", // β
Valid length
};
function handleFormSubmit(formData: unknown) {
const result = ContactFormSchema.safeParse(formData);
if (!result.success) {
return {
success: false,
errors: result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}))
};
}
// Process valid form data
return { success: true, data: result.data };
}
Configuration Validation
const ConfigSchema = Interface({
database: Interface({
host: "string!", // Required non-empty host
port: "number(1,65535)", // Required port with range validation
name: "string(1,50)", // Required database name with length constraint
ssl: "boolean?",
}),
redis: Interface({
url: "url", // Required valid URL (has built-in validation)
ttl: "number(60,)", // Required TTL with minimum constraint
})["?"], // Optional nested object
features: Interface({
enableCache: "boolean",
maxUsers: "number!", // Required non-zero max users
environment: "development|staging|production",
}),
});
// Load and validate configuration
function loadConfig(): ConfigType {
const rawConfig = JSON.parse(process.env.APP_CONFIG || '{}');
const result = ConfigSchema.safeParse(rawConfig);
if (!result.success) {
console.error('Invalid configuration:', result.error.issues);
process.exit(1);
}
return result.data;
}
type ConfigType = typeof ConfigSchema.infer;
π― Use Cases
- API Validation β Validate request/response payloads with automatic typing
- Form Processing β Ensure user input meets requirements before processing
- Configuration Management β Validate app settings and environment variables
- Data Pipelines β Type-safe data transformation and validation
- Database Models β Validate data before database operations
- Integration Testing β Ensure external APIs return expected data structures
π οΈ Error Handling
const result = UserSchema.safeParse(invalidData);
if (!result.success) {
// Access detailed error information
result.error.issues.forEach(issue => {
console.log({
path: issue.path.join('.'), // Field path: "user.email"
code: issue.code, // Error code: "invalid_email"
message: issue.message, // Human readable message
received: issue.received, // Actual value received
expected: issue.expected, // Expected type/format
});
});
}
π VSCode Extension
Enhance your development experience with our VSCode extension:
- Search for "Fortify Schema" in the Extensions marketplace
- Install the extension for syntax highlighting and IntelliSense
- Enjoy autocomplete and validation in your schema definitions
See the GitHub. Report any bugs to team@nehonix.space
Top comments (0)