Validare cu Zod
Scheme, safeParse, coerce pentru FormData, erori per câmp, o schemă pe client și server.
Actualizat
Ce este validarea cu o schemă
Datele care intră în aplicație (formulare, query params, răspunsuri de API, JSON) sunt de tip „nu știu ce e” — unknown. Validarea verifică dacă au forma și regulile așteptate înainte să le folosești.
O schemă descrie forma datelor o singură dată, ca un obiect. Din ea obții trei lucruri:
- validare la runtime (în browser și pe server);
- mesaje de eroare per câmp;
- tipul TypeScript, dedus automat — nu-l mai scrii de mână.
Zod e librăria standard pentru asta în ecosistemul TypeScript.
O schemă
import { z } from 'zod'
export const signupSchema = z.object({
name: z.string().trim().min(2, 'Numele e prea scurt'),
email: z.email('Email invalid'),
age: z.coerce.number().int().min(16, 'Minim 16 ani'),
password: z.string().min(8, 'Minim 8 caractere'),
terms: z.literal('on', 'Trebuie să accepți termenii'),
})
export type SignupInput = z.infer<typeof signupSchema>
// { name: string; email: string; age: number; password: string; terms: 'on' }Tipurile de bază — și ce rezolvă
| Schemă | Validează | Notă |
|---|---|---|
z.string(), z.number(), z.boolean() |
tipul | |
z.email(), z.url(), z.uuid() |
formate de string | în Zod 4 sunt la nivel de top; z.string().email() e învechit |
.min(), .max(), .regex() |
reguli | primesc mesajul ca al doilea argument |
.trim(), .toLowerCase() |
transformă valoarea | rezultatul e deja curățat |
z.coerce.number() |
convertește '42' → 42 înainte de validare |
esențial pentru FormData (totul e string) |
z.enum(['admin', 'editor']) |
una din valori | |
.optional(), .nullable(), .default(x) |
valori lipsă | |
z.array(schema), z.object({...}) |
colecții, obiecte imbricate |
parse vs safeParse
schema.parse(data) |
schema.safeParse(data) |
|
|---|---|---|
| Date valide | returnează datele | { success: true, data } |
| Date invalide | aruncă ZodError |
{ success: false, error } — nu aruncă |
| Când | date care trebuie să fie corecte (config, env) — o eroare e un bug | input de la user — erorile sunt normale |
const result = signupSchema.safeParse(Object.fromEntries(formData))
if (!result.success) {
return { errors: z.flattenError(result.error).fieldErrors }
// { email: ['Email invalid'], age: ['Minim 16 ani'] }
}
await createUser(result.data) // tipat, curățat, convertit| Formatare erori | Rezultat | Când |
|---|---|---|
z.flattenError(error) |
{ formErrors: [], fieldErrors: { email: [...] } } |
formulare simple (un nivel) |
z.treeifyError(error) |
arbore care urmează forma schemei | obiecte imbricate, liste |
Reguli care implică mai multe câmpuri
const schema = z
.object({ password: z.string().min(8), confirm: z.string() })
.refine(d => d.password === d.confirm, { message: 'Parolele nu coincid', path: ['confirm'] })path pune eroarea pe câmpul potrivit.
O schemă, două locuri
Cel mai mare avantaj: aceeași schemă pe client (feedback rapid în formular) și pe server (securitate).
src/features/signup/model/signup-schema.tsschema Zod + tipul dedus
ui/signup-form.tsxuseForm({ resolver: zodResolver(signupSchema) })
api/signup-action.ts'use server' → signupSchema.safeParse(...)
Validarea din client poate fi ocolită oricând. Cea de pe server e cea care contează.
Alternative
| Librărie | De ce ai alege-o |
|---|---|
| Zod | standardul, ecosistem uriaș, tipuri excelente |
| Valibot | API modular, bundle mult mai mic pentru client |
| ArkType | sintaxă apropiată de TypeScript, foarte rapid |
Toate trei implementează Standard Schema — librăriile de formulare le acceptă la fel.
Pe scurt
- Schemă = validare + mesaje + tip TS, scrise o singură dată.
safeParsepentru input de la user;z.coercepentruFormData;z.flattenErrorpentru erori per câmp.- Aceeași schemă în client și pe server; serverul decide.