Fortify Schema

P

ProCoder 😌

Guest

Fortify Schema​

TypeScript-First Validation Library with Intuitive Syntax
Report any bugs to Nehonix-Team via: [email protected]

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(), and extend() methods
  • 🎨 VSCode Integration β€” Dedicated extension with syntax highlighting and IntelliSense
  • πŸ“¦ Zero Dependencies β€” Lightweight and performant

πŸš€ Quick Start​

Installation​


Code:
npm install fortify-schema
# or
yarn add fortify-schema
# or
pnpm add fortify-schema

Requirements: TypeScript 4.5+ and Node.js 14+

Basic Usage​


Code:
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: "[email protected]",    // βœ… 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​


Code:
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!)​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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​


Code:
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: "[email protected]",    // βœ… 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​


Code:
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​


Code:
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:

  1. Search for "Fortify Schema" in the Extensions marketplace
  2. Install the extension for syntax highlighting and IntelliSense
  3. Enjoy autocomplete and validation in your schema definitions

See the GitHub. Report any bugs to [email protected]

Continue reading...
 


Join 𝕋𝕄𝕋 on Telegram
Channel PREVIEW:
Back
Top