Skip to content
Learni
Voir tous les tutoriels
TypeScript

Comment valider des données avec Zod en 2026

Read in English

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

terminal
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/node

On 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

primitives.ts
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

user-schema.ts
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

advanced-schema.ts
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

api-server.ts
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/users

Cré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

transform-schema.ts
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 safeParse en prod : évite crashes sur inputs malveillants.
  • Messages d'erreur en français : { message: 'Email requis' } pour UX locale.
  • Inférez types : z.infer partout 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