Zod Validation
TypeScript-first schema validation, parsing, form integration, error handling.
typescriptvalidationfullstack
# Zod Validation
## Install
```bash
npm install zod
```
## Basic schemas
```ts
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(50),
email: z.string().email(),
age: z.number().int().min(0).max(120).optional(),
role: z.enum(['admin', 'user', 'guest']).default('user'),
tags: z.array(z.string()).max(10).default([]),
createdAt: z.coerce.date(), // coerce string -> Date
})
type User = z.infer<typeof UserSchema> // extract TS type
const result = UserSchema.safeParse(data)
if (result.success) {
console.log(result.data) // typed
} else {
console.log(result.error.issues)
}
```
## String validators
```ts
z.string().min(1).max(100)
z.string().email()
z.string().url()
z.string().regex(/^[a-z-]+$/)
z.string().startsWith('http')
z.string().trim().toLowerCase() // transforms
```
## Transforms & refine
```ts
// Transform
const schema = z.string().transform(s => s.trim().toLowerCase())
// Custom validation
const PasswordSchema = z.object({
password: z.string().min(8),
confirm: z.string(),
}).refine(d => d.password === d.confirm, {
message: 'Passwords do not match',
path: ['confirm'],
})
```
## Unions & discriminated unions
```ts
const Shape = z.discriminatedUnion('type', [
z.object({ type: z.literal('circle'), radius: z.number() }),
z.object({ type: z.literal('rect'), w: z.number(), h: z.number() }),
])
```
## API route usage (Next.js)
```ts
export async function POST(req: Request) {
const body = await req.json()
const parsed = UserSchema.safeParse(body)
if (!parsed.success) {
return Response.json({ errors: parsed.error.flatten() }, { status: 400 })
}
// parsed.data is fully typed
}
```
## React Hook Form + Zod
```tsx
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(UserSchema),
})
```API: /api/skills/zod-validation