Skip to content

Practical Examples

Learn application scenarios of farrow-schema through real cases.

REST API

typescript
import { ObjectType, String, Number, List, Optional } from 'farrow-schema'
import { Validator } from 'farrow-schema/validator'

// Define model
class User extends ObjectType {
  id = String
  name = String
  email = String
  age = Number
  tags = List(String)
}

// Create input (exclude auto-generated fields)
const CreateUserInput = omitObject(User, ['id'])

// API endpoint
app.post('/users', async (req, res) => {
  // Validate input
  const result = Validator.validate(CreateUserInput, req.body)

  if (result.kind === 'Err') {
    return res.status(400).json({
      error: 'Validation failed',
      message: result.value.message,
      path: result.value.path
    })
  }

  // result.value is type-safe
  const newUser = await db.users.create({
    id: generateId(),
    ...result.value
  })

  res.json(newUser)
})

// Update input (all fields optional)
const UpdateUserInput = partialObject(
  omitObject(User, ['id'])
)

app.patch('/users/:id', async (req, res) => {
  const result = Validator.validate(UpdateUserInput, req.body)

  if (result.kind === 'Err') {
    return res.status(400).json({ error: result.value.message })
  }

  const updated = await db.users.update(req.params.id, result.value)
  res.json(updated)
})

Form Validation

typescript
class SignUpForm extends ObjectType {
  username = RegExp(/^[a-zA-Z0-9_]{3,16}$/)
  email = EmailType
  password = StringLength(8, 32)
  confirmPassword = String
}

// Custom validator: check password matching
class SignUpValidator extends ValidatorType<TypeOf<typeof SignUpForm>> {
  validate(input: unknown) {
    const result = Validator.validate(SignUpForm, input)
    if (result.kind === 'Err') return result

    if (result.value.password !== result.value.confirmPassword) {
      return this.Err('Passwords do not match')
    }

    return this.Ok(result.value)
  }
}

// Use in frontend
async function handleSubmit(formData: unknown) {
  const result = Validator.validate(SignUpValidator, formData)

  if (result.kind === 'Err') {
    showError(result.value.message)
    return
  }

  await api.signUp(result.value)
}

Configuration Validation

typescript
import { Union, Literal, Struct } from 'farrow-schema'

const DatabaseConfig = Struct({
  type: Literal('postgres'),
  host: String,
  port: Int,
  database: String,
  user: String,
  password: String
})

const RedisConfig = Struct({
  type: Literal('redis'),
  host: String,
  port: Int,
  password: Optional(String)
})

const AppConfig = Struct({
  port: Int,
  database: Union(DatabaseConfig, RedisConfig),
  logging: Struct({
    level: Union(
      Literal('debug'),
      Literal('info'),
      Literal('warn'),
      Literal('error')
    ),
    format: Union(
      Literal('json'),
      Literal('text')
    )
  })
})

// Load configuration
const config = loadConfigFile('app.json')
const result = Validator.validate(AppConfig, config)

if (result.kind === 'Err') {
  throw new Error(`Invalid config: ${result.value.message} at ${result.value.path}`)
}

// Type-safe configuration
startServer(result.value)

Tree Data Structure

typescript
class Category extends ObjectType {
  id = String
  name = String
  description = Optional(String)
  children = List(Category)     // ✅ Recursive reference
  parent = Optional(Category)
}

// Validate category tree
const categoryTree = {
  id: '1',
  name: 'Electronics',
  description: 'Electronic products',
  children: [
    {
      id: '1-1',
      name: 'Computers',
      children: [
        { id: '1-1-1', name: 'Laptops', children: [] }
      ]
    }
  ]
}

const result = Validator.validate(Category, categoryTree)
if (result.kind === 'Ok') {
  // Recursively traverse category tree
  function traverse(category: TypeOf<typeof Category>) {
    console.log(category.name)
    category.children.forEach(traverse)
  }
  traverse(result.value)
}

Complex Business Scenario: E-commerce Order

typescript
import { ObjectType, String, Number, List, Union, Literal, Optional } from 'farrow-schema'

// Product
class Product extends ObjectType {
  id = String
  name = String
  price = Number
  stock = Int
}

// Order item
class OrderItem extends ObjectType {
  product = Product
  quantity = Int
  subtotal = Number
}

// Address
class Address extends ObjectType {
  street = String
  city = String
  state = String
  zipCode = String
  country = String
}

// Payment method
const PaymentMethod = Union(
  Struct({
    type: Literal('credit_card'),
    cardNumber: String,
    cardHolder: String,
    expiryDate: String
  }),
  Struct({
    type: Literal('paypal'),
    email: String
  }),
  Struct({
    type: Literal('bank_transfer'),
    bankName: String,
    accountNumber: String
  })
)

// Order status
const OrderStatus = Union(
  Literal('pending'),
  Literal('paid'),
  Literal('shipped'),
  Literal('delivered'),
  Literal('cancelled')
)

// Complete order
class Order extends ObjectType {
  id = String
  userId = String
  items = List(OrderItem)
  shippingAddress = Address
  billingAddress = Optional(Address)
  paymentMethod = PaymentMethod
  status = OrderStatus
  total = Number
  createdAt = String
  updatedAt = String
}

// Create order input
const CreateOrderInput = Struct({
  items: List(Struct({
    productId: String,
    quantity: Int
  })),
  shippingAddress: Address,
  billingAddress: Optional(Address),
  paymentMethod: PaymentMethod
})

// API handling
app.post('/orders', async (req, res) => {
  const result = Validator.validate(CreateOrderInput, req.body)

  if (result.kind === 'Err') {
    return res.status(400).json({
      error: 'Invalid order data',
      details: result.value.message,
      path: result.value.path
    })
  }

  const order = await createOrder(result.value)
  res.status(201).json(order)
})

Multi-level Nested Validation

typescript
class Company extends ObjectType {
  id = String
  name = String
  departments = List(Department)
}

class Department extends ObjectType {
  id = String
  name = String
  manager = Employee
  employees = List(Employee)
}

class Employee extends ObjectType {
  id = String
  name = String
  email = String
  position = String
  salary = Number
  projects = List(Project)
}

class Project extends ObjectType {
  id = String
  name = String
  deadline = String
  status = Union(
    Literal('not_started'),
    Literal('in_progress'),
    Literal('completed')
  )
}

// Validate entire company structure
const companyData = loadCompanyData()
const result = Validator.validate(Company, companyData)

if (result.kind === 'Ok') {
  console.log(`Company ${result.value.name} validation passed`)
  console.log(`Total ${result.value.departments.length} departments`)
} else {
  console.error(`Data validation failed: ${result.value.message}`)
  console.error(`Error location: ${result.value.path?.join('.')}`)
}

This is a third-party Farrow documentation site | Built with ❤️ and TypeScript