Skip to content

ADR-0005 Input validation

Company standard validation library — Zod, at every system boundary.

Decision
ZodBuild the app
Status
Accepted
Date
Scope
Company-wide default for all new apps

Every app validates data at its boundaries: API request payloads (server routes, ADR-0036), form submissions, environment variables at boot, URL params, and third-party webhook payloads. TypeScript types are erased at runtime — without runtime validation, any from the network flows straight into business logic.

Requirements:

  • Runtime validation with static type inference — one schema produces both the validator and the TS type
  • Works in our API routes — plain safeParse in server-route handlers
  • Integrates with TanStack Form/Router on the client — via Standard Schema, the shared spec all major validation libs now implement
  • One library everywhere — server and client share schemas; no duplicated validation logic

The incumbent. Schema-first, excellent type inference, enormous ecosystem. v4 shipped a rewritten core with major performance gains and first-party Standard Schema support.

Pros

  • Largest ecosystem and mindshare — every tool has a Zod adapter first
  • First-party Hono integration: @hono/zod-validator
  • Standard Schema compliant → plugs into TanStack Form/Router directly
  • Single source of truth: schema → inferred TS type → Hono validator → form validator
  • @hono/zod-openapi turns schemas into OpenAPI specs
  • Zod 4 core rewrite closes the historical performance gap

Cons

  • Largest client-side bundle of the candidates (mitigated by v4 tree-shaking)
  • API surface is broad; team should stick to a common subset

Modular, tree-shakeable alternative with near-identical API.

Pros

  • Smallest bundles; modular imports
  • Standard Schema compliant

Cons

  • Smaller ecosystem; adapters exist but land later than Zod’s
  • Less Stack Overflow coverage, fewer hiring-pool developers know it

Type-syntax-as-validation, extremely fast.

Pros

  • Fastest validation of the three
  • Elegant: validators written in TypeScript’s own type syntax

Cons

  • Youngest; steepest learning curve (a new DSL, however familiar it looks)
  • Smallest ecosystem and community

Yup is dated and weaker on inference; TypeBox is JSON-Schema-centric (great for OpenAPI, clunkier DX); io-ts is FP-heavy. All viable, none a better default.

Zod is the company-standard validation library, used at every system boundary:

  • API routes — safeParse through readJson and readQuery in the server-route handlers (ADR-0036). (Amended 2026-11-13: this ADR first named @hono/zod-validator; ADR-0036 replaced Hono with Start’s server routes.)
  • Server functions — the schema passed to .validator(); they are endpoints anyone can call, not just our UI
  • Env vars — parsed once at boot; the app crashes fast on misconfiguration
  • Forms — shared schemas feed TanStack Form through Standard Schema
  • Webhooks / external payloads — validated at ingress, never trusted

The deciding factors: the largest ecosystem, first-party adapters for everything in our stack (TanStack, and Hono where a product adopts it), Standard Schema compliance keeping us portable, and one-schema-one-type as the single source of truth. We accept the bundle-size cost, mitigated by Zod 4 tree-shaking and by sharing schemas instead of duplicating them.

Easier:

  • API payloads, env config, and forms all speak one schema language — less context-switching
  • Schemas are shared between client and server; drift between client-side and server-side validation becomes impossible
  • OpenAPI generation, for a product that publishes an API (ADR-0036), comes nearly free from the same schemas
  • Boot-time env validation catches ops misconfiguration before the first request

Harder:

  • Client bundles carry a validation lib — acceptable, but schemas should be shared and tree-shaken, not pasted per-form
  • Discipline required to validate at boundaries only — not re-validating already-trusted internal data

Given up: Valibot’s smaller bundles and ArkType’s raw speed. Accepted — ecosystem gravity and integration coverage outweigh both at our scale.