Partager un schéma Zod unique entre votre frontend React et votre backend Node
Découvrez comment un seul schéma Zod peut valider les formulaires React, les réponses API, les corps de requête Express et les variables d’environnement, tout en générant des types TypeScript correspondants.
La validation est essentielle dans toute application, mais les équipes ont souvent tendance à l’intégrer de manière fragmentée : une bibliothèque pour le frontend, une autre pour le backend, avec les mêmes règles copiées-collées en plusieurs endroits. Zod est devenu la bibliothèque préférée des développeurs JavaScript et TypeScript précisément parce qu’il évite ce désordre : vous écrivez un seul schéma, qui sert à la fois à vérifier vos données et à générer le type TypeScript correspondant, prêt à être utilisé de manière identique dans le navigateur et sur le serveur.
1. Qu’est-ce que Zod ?
Zod est une bibliothèque de schémas conçue dès le départ pour TypeScript. Vous décrivez une seule fois la structure de vos données, et Zod utilise cette description pour vérifier les valeurs en temps de exécution et pour dériver automatiquement un type TypeScript — il n’y a pas d’interface séparée à écrire, et aucun risque que celle-ci ne soit en décalage avec vos règles de validation.
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }
Un seul schéma de ce type couvre simultanément trois fonctions : il documente la structure de vos données, la valide en temps de exécution et fournit le type statique sur lequel s’appuient votre éditeur et votre compilateur.
2. Pourquoi Zod est supérieur aux alternatives
L’avantage majeur réside dans l’inférence automatique des types. Des bibliothèques comme Yup ou Joi exigent généralement que vous mainteniez un schéma de validation en parallèle d’une interface TypeScript écrite manuellement, en espérant que ces deux éléments ne s’éloigneront pas au fil des modifications du code. Zod élimine complètement ce risque : le type est dérivé directement du schéma, il n’y a donc rien à synchroniser.
Zod est également léger et ne comporte aucune dépendance externe, ce qui en fait un outil tout aussi adapté pour des bundles frontend où l’espace est limité que pour des services Node.js. Son API chainable et composable permet également à des validations complexes — objets imbriqués, unions, champs dépendants les uns des autres — de rester claires et lues facilement, sans se transformer en un enchevêtrement de fonctions d’aide ad hoc.
3. Utiliser Zod dans une application React
3.1 Validation des formulaires avec React Hook Form
Zod s’intègre directement à React Hook Form via le package @hookform/resolvers.
npm install zod react-hook-form @hookform/resolvers
// components/SignupForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const SignupSchema = z.object({
name: z.string().min(2, 'Name is too short'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type SignupData = z.infer<typeof SignupSchema>;
export function SignupForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupData>({
resolver: zodResolver(SignupSchema),
});
const onSubmit = (data: SignupData) => {
console.log('Valid data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} placeholder="Password" />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
Il n’y a pas de suivi manuel de l’état d’erreur ni de déclarations de type redondantes à gérer — un seul schéma s’occupe en même temps de la validation, fournit les messages d’erreur affichés à côté de chaque champ et définit le type TypeScript de l’objet data soumis.
3.2 Validation des réponses API
Zod est tout aussi utile du côté entrant de votre application — par exemple, pour vérifier que les données retournées par une API correspondent bien à ce que vous attendez, car on ne peut pas compter uniquement sur les types en temps de compilation pour en garantir la conformité.
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
});
const PostsResponseSchema = z.array(PostSchema);
async function fetchPosts() {
const res = await fetch('/api/posts');
const json = await res.json();
const result = PostsResponseSchema.safeParse(json);
if (!result.success) {
console.error(result.error.flatten());
throw new Error('Invalid API response shape');
}
return result.data; // fully typed Post[]
}
Cette approche vous permet de détecter les réponses mal formatées ou inattendues avant qu’elles ne provoquent des échecs silencieux dans votre interface utilisateur.
4. Utilisation de Zod dans un backend Node.js / Express
4.1 Validation des corps de requête
npm install zod express
// schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export function validate(schema: ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
req.body = result.data;
next();
};
}
// routes/users.ts
import { Router } from 'express';
import { validate } from '../middleware/validate';
import { CreateUserSchema } from '../schemas/user-schema';
const router = Router();
router.post('/users', validate(CreateUserSchema), (req, res) => {
// req.body is now guaranteed to match CreateUserInput
const { name, email, age } = req.body;
res.status(201).json({ name, email, age });
});
export default router;
Cette configuration offre à chaque route une étape de validation déclarative uniforme, avec un traitement centralisé des erreurs plutôt que leur répétition sous forme de vérifications if intégrées dans les gestionnaires.
4.2 Validation des variables d’environnement
Une utilisation sous-estimée mais puissante de Zod consiste à vérifier process.env au démarrage de l’application, afin que des configurations incorrectes provoquent une erreur immédiate plutôt qu’un bug confus plus tard.
// config/env.ts
import { z } from 'zod';
const EnvSchema = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = EnvSchema.parse(process.env);
Si une variable nécessaire manque ou n’a pas le bon format, le processus plante immédiatement avec un message d’erreur lisible — ce qui est bien plus facile à diagnostiquer qu’une panne mystérieuse apparue lors d’une requête à la base de données.
5. Le véritable avantage : un schéma unique partagé dans toute l’application
Puisque les schémas Zod ne sont que des valeurs TypeScript, rien ne vous empêche de les placer dans un package partagé — ou dans un dossier commun au sein d’un monorepo — et de réutiliser le même schéma tant du côté client que serveur.
/packages
/shared
/schemas
user-schema.ts <-- used by both React app and Express API
/web (React/Next.js)
/api (Node/Express)
// packages/shared/schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
L’application React s’appuie sur ce schéma pour vérifier le formulaire de inscription avant son envoi. L’API Express utilise exactement le même schéma pour valider la charge de travail reçue. Lorsque le schéma évolue — par exemple, l’ajout d’un champ obligatoire — les deux couches intègrent simultanément ce changement, et TypeScript met immédiatement en évidence tout code qui n’a pas encore été adapté à la nouvelle structure. Cela permet d’éviter toute une catégorie de bugs où la validation côté client et côté serveur s’éloignent progressivement l’une de l’autre.
6. Bonnes pratiques
- Préférez
safeParselorsque l’échec est un résultat normal et attendu (entrées de formulaire, réponses d’API tierces), et réservezparse— qui lance une exception — aux cas où la donnée ne doit absolument jamais être invalide, comme les variables d’environnement vérifiées au démarrage.
.transform() pour nettoyer les données dans le cadre même de la validation — en supprimant les espaces inutiles, en forçant les types — plutôt que d’exécuter une étape de normalisation distincte par la suite.z.infer aux interfaces écrites manuellement pour tout ce qui est déjà pris en charge par un schéma, de sorte que vos types et votre logique de validation ne soient jamais désynchronisés.error.flatten() ou error.format() dans les réponses d’erreur API, ce qui permet au code frontend de mapper facilement chaque erreur au bon champ de formulaire.7. Conclusion
Zod est bien plus qu’une bibliothèque de validation typique — il transforme complètement la relation entre validation et typage. En générant des types TypeScript directement à partir des schémas en temps de exécution, il élimine entièrement le problème des définitions de types et des règles de validation qui divergent silencieusement. Ajoutez à cela son faible encombrement, sa conception composable et son comportement cohérent que ce soit dans le navigateur ou sous Node, et Zod devient une solution idéale pour les projets TypeScript full-stack basés sur React et Node.js.
Prochaines étapes :
- Examinez
zod-to-openapisi vous avez besoin de générer des documents OpenAPI directement à partir de vos schémas - Découvrez
.refine()et.superRefine()pour créer une logique de validation personnalisée couvrant plusieurs champs - Jetez un coup d’œil à tRPC, qui s’appuie nativement sur les schémas Zod pour assurer une sécurité de type du début à la fin dans votre API
Lectures complémentaires
- Zod vs express-validator : Deux approches pour la validation Express — Compare la validation des requêtes basée sur des schémas avec Zod à celle utilisant les middleware express-validator en chaîne, en abordant la configuration, le formatage des erreurs et les pièges courants.
- Les propositions TC39 en 2026 : Décorateurs, Temporal et Signals expliqués — Une présentation pratique de trois propositions TC39 — les décorateurs natifs, l’API Temporal et Signals — ainsi que leur impact sur les développeurs JavaScript et TypeScript full-stack.