β Data Validation β
Validating input at system boundaries to prevent bugs and security issues.
Where to validate β
| Boundary | What to validate | Tool |
|---|---|---|
| API input | Request body, params, query | Zod, Joi, class-validator |
| Form input | User-entered data | React Hook Form + Zod |
| Database | Schema constraints | DB constraints + migrations |
| External API | Response shape | Zod, runtime checks |
| Config / Env | Environment variables | Zod 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 invalidValidation 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 β
| Field | Rules |
|---|---|
| Valid format, max length | |
| Password | Min length, complexity rules |
| URL | Valid URL format, allowed protocols |
| Phone | Valid format, E.164 standard |
| Date | Valid date, reasonable range |
| ID | UUID format or positive integer |
| Free text | Max length, sanitize HTML |
| File upload | Max 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