Skip to content

βœ… Data Validation ​

Validating input at system boundaries to prevent bugs and security issues.

Where to validate ​

BoundaryWhat to validateTool
API inputRequest body, params, queryZod, Joi, class-validator
Form inputUser-entered dataReact Hook Form + Zod
DatabaseSchema constraintsDB constraints + migrations
External APIResponse shapeZod, runtime checks
Config / EnvEnvironment variablesZod at startup

Zod examples ​

typescript
import { z } from 'zod'

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['admin', 'user', 'viewer']),
})

type CreateUserInput = z.infer<typeof CreateUserSchema>

// In controller
const input = CreateUserSchema.parse(req.body)
// Throws ZodError if invalid

Validation layers ​

Client-side validation (UX β€” fast feedback)
    ↓
API input validation (Security β€” trust nothing)
    ↓
Business logic validation (Domain β€” rules enforcement)
    ↓
Database constraints (Safety net β€” last line of defense)

Never rely on client-side validation alone β€” it can be bypassed.

Common validations ​

FieldRules
EmailValid format, max length
PasswordMin length, complexity rules
URLValid URL format, allowed protocols
PhoneValid format, E.164 standard
DateValid date, reasonable range
IDUUID format or positive integer
Free textMax length, sanitize HTML
File uploadMax size, allowed MIME types

Error messages ​

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "details": [
      { "field": "email", "message": "Invalid email format" },
      { "field": "age", "message": "Must be a positive number" }
    ]
  }
}
  • Return all validation errors at once, not one at a time
  • Use human-readable messages
  • Include the field name for client-side mapping

Pergame Knowledge Base