Validation avec Zod
Zod est la bibliothèque de validation TypeScript la plus populaire. Elle offre une validation type-safe avec une excellente expérience développeur.
Pourquoi Zod ?
Zod est devenu le standard de l'industrie pour la validation en TypeScript :
- ✅ Type-safe : Les types TypeScript sont automatiquement inférés
- ✅ Zero-dependency : Aucune dépendance externe
- ✅ Petite taille : ~8kb minifié
- ✅ Très populaire : Utilisé par Vercel, Clerk, Prisma, tRPC, etc.
- ✅ Messages d'erreur clairs : Personnalisables et précis
Installation
npm install zod
C'est tout ! Zod n'a aucune dépendance et fonctionne immédiatement avec TypeScript.
Concepts de base
Créer un schéma
Un schéma définit la structure et les règles de validation de vos données.
import { z } from 'zod';
// Schéma simple
const emailSchema = z.string().email();
// Schéma d'objet
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(18)
});
Valider des données
// ✅ Validation réussie
const result = emailSchema.safeParse('user@example.com');
console.log(result.success); // true
console.log(result.data); // "user@example.com"
// ❌ Validation échouée
const result2 = emailSchema.safeParse('invalid-email');
console.log(result2.success); // false
console.log(result2.error); // ZodError avec détails
safeParse vs parse
Utilisez toujours safeParse() plutôt que parse() :
safeParse()retourne{ success: boolean, data?: T, error?: ZodError }parse()lance une exception si la validation échoue
safeParse() est plus sûr et plus facile à gérer dans React !
Quelle est la différence principale entre safeParse() et parse() ?
Inférence de types TypeScript
Zod peut automatiquement créer les types TypeScript à partir de vos schémas.
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number()
});
// Type automatiquement inféré !
type User = z.infer<typeof userSchema>;
// équivalent à :
// type User = { name: string; email: string; age: number }
const user: User = {
name: 'Alice',
email: 'alice@example.com',
age: 25
};
Que fait z.infer<typeof userSchema> ?
Types de validation
Strings
const schema = z.string()
.min(3, { message: 'Au moins 3 caractères' })
.max(20, { message: 'Maximum 20 caractères' })
.email({ message: 'Email invalide' })
.url({ message: 'URL invalide' })
.regex(/^[a-z]+$/, { message: 'Seulement des lettres minuscules' })
.trim(); // Supprime les espaces
Validations courantes :
z.string().email(); // Email valide
z.string().url(); // URL valide
z.string().min(5); // Minimum 5 caractères
z.string().max(100); // Maximum 100 caractères
Comment personnaliser les messages d'erreur dans Zod ?
Numbers
const schema = z.number()
.min(0, { message: 'Doit être positif' })
.max(100, { message: 'Maximum 100' })
.int({ message: 'Doit être un entier' })
.positive({ message: 'Doit être positif' });
Booleans, Dates et Enums
// Boolean
z.boolean();
// Date
z.date()
.min(new Date('2024-01-01'))
.max(new Date('2025-12-31'));
// Enum (valeurs limitées)
const roleSchema = z.enum(['admin', 'user', 'guest']);
type Role = z.infer<typeof roleSchema>; // 'admin' | 'user' | 'guest'
Arrays et Objects
// Array
const tagsSchema = z.array(z.string())
.min(1, { message: 'Au moins un tag' })
.max(5, { message: 'Maximum 5 tags' });
// Object
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(18)
});
Validation avancée
Champs optionnels et valeurs par défaut
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().optional(), // Peut être undefined
bio: z.string().nullable(), // Peut être null
theme: z.string().default('light') // Valeur par défaut
});
Quelle est la différence entre .optional() et .nullable() ?
Validation personnalisée avec refine()
Utilisez .refine() pour des règles de validation personnalisées.
// Validation d'un seul champ
const passwordSchema = z.string()
.min(8)
.refine(password => /[A-Z]/.test(password), {
message: 'Doit contenir au moins une majuscule'
});
// Comparaison entre champs
const signupSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
message: 'Les mots de passe ne correspondent pas',
path: ['confirmPassword']
});
Utilisation avec React
Formulaire de connexion avec Zod
Voici un exemple complet d'utilisation de Zod dans un formulaire React.
import { useState } from 'react';
import { z } from 'zod';
// 1. Définir le schéma
const loginSchema = z.object({
email: z.string()
.min(1, { message: 'Email requis' })
.email({ message: 'Email invalide' }),
password: z.string()
.min(8, { message: 'Au moins 8 caractères' })
});
// 2. Inférer le type TypeScript
type LoginForm = z.infer<typeof loginSchema>;
function LoginForm() {
const [formData, setFormData] = useState<LoginForm>({
email: '',
password: ''
});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// 3. Valider avec Zod
const result = loginSchema.safeParse(formData);
if (!result.success) {
// Extraire les erreurs
const fieldErrors: Record<string, string> = {};
result.error.errors.forEach(err => {
if (err.path[0]) {
fieldErrors[err.path[0].toString()] = err.message;
}
});
setErrors(fieldErrors);
return;
}
// 4. Données validées et type-safe !
console.log('Données valides:', result.data);
setErrors({});
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
{errors.email && <span className="error">{errors.email}</span>}
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Mot de passe"
/>
{errors.password && <span className="error">{errors.password}</span>}
<button type="submit">Se connecter</button>
</form>
);
}
Exemple : Schéma d'inscription plus complexe
const signupSchema = z.object({
username: z.string()
.min(3, { message: 'Au moins 3 caractères' })
.max(20, { message: 'Maximum 20 caractères' }),
email: z.string().email({ message: 'Email invalide' }),
password: z.string()
.min(8, { message: 'Au moins 8 caractères' })
.regex(/[A-Z]/, { message: 'Au moins une majuscule' }),
confirmPassword: z.string(),
age: z.number().int().min(13)
}).refine(data => data.password === data.confirmPassword, {
message: 'Les mots de passe ne correspondent pas',
path: ['confirmPassword']
});
type SignupForm = z.infer<typeof signupSchema>;
Zod + React Hook Form (Optionnel)
Section avancée
Cette section est optionnelle pour les débutants. Vous pouvez d'abord maîtriser Zod avec useState avant d'explorer React Hook Form.
React Hook Form s'intègre parfaitement avec Zod via @hookform/resolvers.
npm install react-hook-form @hookform/resolvers
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
type FormData = z.infer<typeof schema>;
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm<FormData>({
resolver: zodResolver(schema)
});
const onSubmit = (data: FormData) => {
console.log(data); // Type-safe et validé !
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Se connecter</button>
</form>
);
}
React Hook Form + Zod = Combo parfait
React Hook Form gère les formulaires (performance, re-renders) Zod gère la validation (type-safe, règles)
Ensemble, ils forment la meilleure solution pour les formulaires complexes en React !
Pourquoi utiliser Zod ?
Avantages de Zod par rapport à la validation manuelle
Validation manuelle : Code répétitif, regex manuelles, pas de types automatiques
// ❌ Validation manuelle
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.email = 'Email invalide';
}
Avec Zod : Concis, type-safe, facile à maintenir
// ✅ Avec Zod
const schema = z.object({
email: z.string().email('Email invalide'),
password: z.string().min(8)
});
type Form = z.infer<typeof schema>; // Types automatiques !
const result = schema.safeParse(formData);
Avantages clés :
- ✅ Moins de code
- ✅ Types TypeScript automatiques
- ✅ Messages d'erreur structurés
- ✅ Facile à maintenir
Cas d'usage réels
Valider les données d'une API
Zod est idéal pour valider les réponses d'API et garantir la sécurité des types.
const apiResponseSchema = z.object({
users: z.array(z.object({
id: z.number(),
name: z.string(),
email: z.string().email()
}))
});
const response = await fetch('/api/users');
const data = await response.json();
// Valider les données de l'API
const result = apiResponseSchema.safeParse(data);
if (!result.success) {
console.error('Données API invalides:', result.error);
return;
}
// Données type-safe !
const users = result.data.users;
Alternatives à Zod
Autres bibliothèques de validation
Zod n'est pas la seule option. L'alternative principale est :
Yup (https://github.com/jquense/yup)
- Très populaire, existe depuis longtemps
- Syntaxe similaire à Zod
- Moins bon support TypeScript
- Souvent utilisé avec Formik
Pourquoi choisir Zod ?
- ✅ Type-safe avec TypeScript
- ✅ Zero-dependency
- ✅ Excellent écosystème (tRPC, Prisma, Vercel)
- ✅ Standard moderne pour React + TypeScript
Bonnes pratiques
Conseils pour bien utiliser Zod
1. Définir les schémas en dehors des composants
// ✅ Bon : schéma réutilisable
const userSchema = z.object({ ... });
function Component() {
// Utiliser le schéma ici
}
2. Réutiliser les schémas
const addressSchema = z.object({
street: z.string(),
city: z.string()
});
const userSchema = z.object({
name: z.string(),
address: addressSchema // Réutilisation !
});
3. Messages d'erreur personnalisés
const schema = z.string().min(3, {
message: 'Le nom doit contenir au moins 3 caractères'
});
4. Utiliser safeParse() au lieu de parse()
// ✅ Recommandé : safeParse ne lance pas d'exception
const result = schema.safeParse(data);
// ❌ Éviter : parse lance une exception
const data = schema.parse(input);
Récapitulatif
Ce que vous avez appris
Zod - Validation moderne et type-safe :
- Installation :
npm install zod - Schémas : Définir la structure des données avec
z.object(),z.string(), etc. - Validation : Utiliser
safeParse()pour valider les données - Types : Inférer automatiquement les types TypeScript avec
z.infer - React : Intégrer Zod dans vos formulaires React
- React Hook Form : Combo parfait avec
@hookform/resolvers/zod - Avantages : Type-safe, concis, maintenable, messages clairs
Pourquoi utiliser Zod ?
- ✅ Moins de code que la validation manuelle
- ✅ Types TypeScript automatiques
- ✅ Validation côté client ET serveur
- ✅ Standard de l'industrie
Prochaine étape : apprendre à partager l'état entre composants (lifting state up) !