Skip to content

Validation

The validation module provides stateless, rule-based input validation. It works with any plain object — form data, JSON bodies, URL params — and returns a typed result you can inspect without throwing exceptions.

Import

ts
import { validate, defineRules } from '@loewen-digital/fullstack/validation'

Basic usage

ts
import { validate } from '@loewen-digital/fullstack/validation'

const result = validate(
  { email: 'alice@example.com', password: 'secret123' },
  {
    email: ['required', 'email'],
    password: ['required', 'min:8'],
  }
)

if (!result.passes) {
  console.log(result.errors)
  // { password: ['The password must be at least 8 characters.'] }
}

// result.data is the validated (and trimmed) input
console.log(result.data.email) // 'alice@example.com'

Working with FormData

ts
const formData = await request.formData()

const result = validate(Object.fromEntries(formData), {
  username: ['required', 'string', 'min:3', 'max:32'],
  email: ['required', 'email'],
  age: ['required', 'numeric', 'min:18'],
})

Available rules

RuleDescription
requiredField must be present and non-empty
stringMust be a string
numericMust be a number or numeric string
booleanMust be a boolean-like value
emailMust be a valid email address
urlMust be a valid URL
min:nMinimum length (strings) or value (numbers)
max:nMaximum length (strings) or value (numbers)
between:min,maxValue must be between min and max
in:a,b,cValue must be one of the listed options
not_in:a,b,cValue must not be one of the listed options
regex:/pattern/Must match the given regular expression
confirmedField must have a matching _confirmation field
nullableField may be null or absent
arrayMust be an array
dateMust be a parseable date string

Custom rules

ts
import { defineRules, validate } from '@loewen-digital/fullstack/validation'

const rules = defineRules({
  slug: (value) => {
    if (!/^[a-z0-9-]+$/.test(String(value))) {
      return 'The :attribute may only contain lowercase letters, numbers, and hyphens.'
    }
  },
})

const result = validate({ slug: 'Hello World' }, { slug: ['required', 'slug'] }, { rules })

Config options

validate() accepts an optional third argument:

OptionTypeDefaultDescription
rulesRecord<string, RuleFn>{}Custom rule definitions
messagesRecord<string, string>{}Custom error message overrides
bailbooleanfalseStop after the first failure per field