Introduction
En 2026, la validation de données reste un pilier de tout projet robuste, surtout avec l'essor des APIs REST et des applications full-stack en TypeScript. Zod, la bibliothèque la plus populaire pour cela, excelle par sa simplicité et son inférence de types automatique : un schéma Zod génère instantanément un type TypeScript correspondant, éliminant les doublons et les erreurs de typage.
Pourquoi Zod surpasse-t-il les alternatives comme Yup ou Joi ? Il est zéro dépendances, ultra-léger (10kB gzippé), et intègre parfaitement Next.js, React ou Node.js. Imaginez valider un formulaire utilisateur en une ligne, avec des messages d'erreur en français, sans boilerplate. Ce tutoriel beginner vous emmène des bases aux cas avancés : installation, schémas primitifs, objets complexes, unions, et intégration pratique.
À la fin, vous saurez sécuriser vos données comme un pro, évitant injections SQL ou payloads malveillants. Prêt à booster votre code ? (128 mots)
Prérequis
- Node.js 20+ installé
- Connaissances de base en TypeScript (types primitifs, objets)
- Un éditeur comme VS Code avec extension TypeScript
- npm ou yarn pour les packages
Installation de Zod
mkdir zod-validation-demo
cd zod-validation-demo
npm init -y
npm install zod
tsx --version || npm install -g tsx
npm install -D typescript @types/nodeOn crée un projet Node.js minimal, installe Zod comme dépendance, et tsx pour exécuter du TS directement (plus rapide que tsc). TypeScript est en devDep pour l'édition. Lancez tsx index.ts pour tester les scripts suivants.
Premier schéma : validation de base
Commençons par les primitives. Zod utilise une API fluide : z.string(), z.number() etc. La méthode .parse() valide et lève une erreur si invalide, tandis que .safeParse() retourne un résultat sans crash.
Analogie : Comme un portier qui vérifie votre ID – strict mais poli.
Schéma primitif simple
import { z } from 'zod';
const EmailSchema = z.string().email({ message: 'Email invalide' });
const AgeSchema = z.number().min(18, { message: 'Âge minimum 18 ans' }).max(120);
const result1 = EmailSchema.safeParse('user@example.com');
const result2 = AgeSchema.safeParse('25');
const result3 = AgeSchema.safeParse(17);
console.log('Email valide:', result1.success);
console.log('Âge 25 valide:', result2.success);
console.log('Âge 17:', result3.error?.issues[0].message);Ce script valide un email et un âge avec messages custom en français. safeParse évite les crashes en production. L'inférence TypeScript : EmailSchema devient string typé. Exécutez avec tsx primitives.ts pour voir : Email valide, Âge OK, erreur sur 17.
Validation d'objets structurés
Pour des formulaires ou JSON API, z.object() combine schémas. Ajoutez .strict() pour rejeter props extras, ou .passthrough() pour les ignorer. Inférence magique : UserSchema infère le type complet.
Schéma objet utilisateur
import { z } from 'zod';
type User = z.infer<typeof UserSchema>; // Inférence auto
const UserSchema = z.object({
name: z.string().min(2, 'Nom trop court'),
email: z.string().email('Email invalide'),
age: z.number().int().min(18),
isAdmin: z.boolean().default(false),
});
const userData = {
name: 'Alice',
email: 'alice@test.com',
age: 28,
isAdmin: true,
extraProp: 'ignore' // Sera rejeté en strict
};
const result = UserSchema.strict().safeParse(userData);
if (result.success) {
console.log('Utilisateur valide:', result.data);
} else {
console.log('Erreurs:', result.error.format());
}Définit un schéma User avec defaults et strict mode (rejette extraProp). z.infer crée le type TS. error.format() donne un output lisible. Parfait pour APIs : valide et type le payload en un appel.
Schémas avancés : arrays, unions, optionnels
Gérez listes (z.array()), choix (z.union()), et champs optionnels (.optional()). Analogie : Un menu restaurant – choisissez entrée OU plat, ou rien.
Schémas complexes avec arrays et unions
import { z } from 'zod';
const RoleSchema = z.union([z.literal('admin'), z.literal('user'), z.literal('guest')]);
const TagsSchema = z.array(z.string().min(1)).max(5);
const PostSchema = z.object({
title: z.string().min(5),
content: z.string(),
role: RoleSchema,
tags: TagsSchema.optional(),
published: z.boolean().default(false),
});
type Post = z.infer<typeof PostSchema>;
const postData = {
title: 'Mon premier post',
content: 'Contenu détaillé...',
role: 'admin' as const,
tags: ['tech', 'zod'],
};
const result = PostSchema.safeParse(postData);
console.log(result.success ? 'Post OK' : result.error.issues);Combine union pour rôles enum-like, array limité, et optional. z.literal pour valeurs exactes. Infère Post avec tags?: string[]. Idéal pour blogs ou dashboards : valide payloads variés sans effort.
Intégration avec une API Express
Zod brille en backend. Utilisez z.object().parse(req.body) dans middlewares pour typer handlers automatiquement.
Middleware Zod pour API
import express from 'express';
import cors from 'cors';
import { z } from 'zod';
const app = express();
app.use(cors());
app.use(express.json());
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
app.post('/users', (req, res) => {
try {
const userData = CreateUserSchema.parse(req.body);
// Simule DB save
res.json({ success: true, user: userData });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
res.status(500).json({ error: 'Erreur serveur' });
}
});
app.listen(3000, () => console.log('Serveur sur http://localhost:3000'));
// Test: curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob","email":"bob@test.com"}' http://localhost:3000/usersCrée un endpoint POST sécurisé. parse() throw si invalide, catch pour 400. Ajoutez npm i express cors @types/express @types/cors -D typescript. Lancez tsx api-server.ts, testez avec curl. Types inférés pour req.body !
Transformations et raffinements
Zod permet .transform() pour normaliser (ex: string to number) et .refine() pour validations custom.
Transform et refine custom
import { z } from 'zod';
const PhoneSchema = z.string().transform((val) => parseInt(val.replace(/\D/g, '')))
.pipe(z.number().min(1000000000, 'Numéro trop court'));
const ProfileSchema = z.object({
phone: PhoneSchema,
score: z.number().refine((val) => val >= 0 && val <= 100, {
message: 'Score entre 0 et 100',
}),
});
const data = { phone: '+33 1 23 45 67 89', score: 85 };
const result = ProfileSchema.safeParse(data);
console.log(result.success ? `Phone: ${result.data.phone}, Score: ${result.data.score}` : result.error.issues);.transform() nettoie le phone en number, .pipe() chaîne validations. .refine() pour logique business. Sortie : Phone: 33123456789, Score:85. Puissant pour sanitiser inputs user.
Bonnes pratiques
- Toujours utiliser
safeParseen prod : évite crashes sur inputs malveillants. - Messages d'erreur en français :
{ message: 'Email requis' }pour UX locale. - Inférez types :
z.inferpartout pour zero duplication. - Schémas réutilisables : Exportez-les en module séparé pour frontend/backend.
- Combine avec tRPC ou React Hook Form : Zod resolvers natifs pour full-stack.
Erreurs courantes à éviter
- Oublier
.strict(): Permet props indésirables, vulnérabilité injection. - Utiliser
parse()sans try/catch : Crashe l'app sur mauvais input. - Ignorer inférence TS : Dupliquer types manuellement = maintenance cauchemar.
- Pas de
.optional()sur arrays : Échoue si undefined, utilisez.default([]).
Pour aller plus loin
- Docs officielles : zod.dev
- Intégrez avec Next.js App Router : Tutoriel Next+Zod
- Avancé : Zod + SuperJSON pour DB queries
- Découvrez nos formations Learni sur TypeScript avancé pour masteriser Zod en prod.