Go somewhere.

Home Page About Page Projects Page CV Page Dev Page Journal Page Contact Page Astro for Documentation and a Professional Site Dev Codex anywhere with tmux, Mosh, Termius, and Tailscale Dev Observing Codex MCP Tool Calls with Langfuse Dev Localization in Product Apps Dev MCP as a Safe AI Integration Boundary Dev Zod, OpenAPI, and Swagger for API Contracts Dev pg-boss for Durable Background Jobs Dev pgvector and RAG, Explained Through a Real Knowledge Workflow Dev Pragmatic Drag and Drop for Real Ordering Tasks Dev Prisma and PostgreSQL as the Product Source of Truth Dev Ralph Loop as a Plan Queue Dev React Router for Full-Stack Product Workflows Dev shadcn-Style UI as an Owned Product System Dev Dense Operational UI with Tables and Editors Dev Terminal Spike: a Native Android Terminal Dev Input Chinese pinyin with tones on Linux with fcitx Dev How to configure the DEFT Pro trackball on Linux Dev Use Pocket (read it later) on KOReader Dev Vercel AI SDK with Explicit Tool Boundaries Dev Vertical Slice Architecture with Dependency-Cruiser Dev Testing Product Workflows with Vitest and Playwright Dev Zod Beyond Validation Dev The Brothers Karamazov is the best novel I have ever read Journal Oral history of two Christians in a Chinese labour camp in the 1960s Journal The Word in the Bible Journal We love because He first loved us, not meaningless self-love Journal Mom, let me take the blame for Dad's mistakes Journal My story with God Journal The world is far from God, close to China Journal The Five Love Languages and Extrovert Only Exist in Language Journal Personality types don't exist, life is not a matching game Journal Dangerous words, why psychology is impossible Journal The story of a Shenzhen worker in 2000 Journal A Conversation with a Driver in Ras Al Khaimah Journal Shadian: a 1975 conflict between Communist forces and Muslims in China Journal Documentaries About China Journal
← Dev/Engineering

Zod Beyond Validation

How runtime schemas can document boundaries, normalize inputs, and keep API and UI contracts consistent.

Zod is usually introduced as a validation library. That is true, but the more useful idea is boundary definition.

A TypeScript type only helps after data is already inside the program. Zod helps at the edge, where data arrives from a form, API request, environment variable, import file, or AI tool call.

const ImportRowSchema = z.object({
  name: z.string().trim().min(1),
  quantity: z.coerce.number().int().positive(),
});

This schema validates, but it also documents what the boundary expects. The trim and coercion are small normalization decisions. They should be intentional because they change what the product accepts.

I like schemas most when they are close to the boundary they protect. A form schema should describe the form input. An API schema should describe the public request. A database model should not automatically become the external contract.

That separation matters. The database may have fields the user should not send. The API may accept a simpler shape than the internal model. A form may represent numbers as strings before parsing. Reusing one schema everywhere can look efficient while hiding different responsibilities.

Zod also helps with consistent error handling. If several routes parse input in the same way, the app can return field errors in a predictable shape.

{
  "fields": {
    "quantity": "Quantity must be a positive number."
  }
}

The exact format is less important than consistency. Users and API clients should not have to learn a new error shape for every endpoint.

The trade-off is that schemas can become too clever. Large transforms, hidden defaults, and complex refinements can make it hard to see what is accepted. When a schema starts to contain real product decisions, I usually move that decision into a service and keep the schema focused on the boundary.

I would use Zod for:

  • form input
  • API request bodies
  • query parameters
  • imported rows
  • environment variables
  • AI tool arguments

I would avoid treating Zod as the whole domain model. It is excellent at the edge. The rest of the app still needs clear services, permissions, and persistence rules.

The practical test is whether the schema makes the boundary easier to review. If it does, it is helping. If it hides the workflow behind a pile of transformations, it is doing too much.