Mongoose : validation et hooks
Validateurs intégrés
Section intitulée « Validateurs intégrés »const schemaClient = new mongoose.Schema({ nom: { type: String, required: [true, 'le nom est obligatoire'], minlength: 2, maxlength: 100, }, email: { type: String, required: true, match: [/^[^@\s]+@[^@\s]+\.[^@\s]+$/, 'adresse e-mail invalide'], }, age: { type: Number, min: [0, 'âge négatif'], max: [130, 'âge irréaliste'] }, statut: { type: String, enum: { values: ['actif', 'suspendu'], message: '{VALUE} non autorisé' }, },});| Validateur | Types |
|---|---|
required |
Tous |
min, max |
Number, Date |
minlength, maxlength, match, enum |
String |
Validateur personnalisé
Section intitulée « Validateur personnalisé »const schemaCommande = new mongoose.Schema({ lignes: { type: [schemaLigne], validate: { validator: (lignes) => lignes.length > 0, message: 'une commande doit contenir au moins une ligne', }, }, telephone: { type: String, validate: { validator: (v) => /^\+?\d{9,15}$/.test(v), message: (props) => `${props.value} n'est pas un numéro valide`, }, },});Un validateur peut être asynchrone (async validator), par exemple pour vérifier l’existence d’une référence ; limiter ces vérifications, elles ajoutent des requêtes.
Gérer les erreurs de validation
Section intitulée « Gérer les erreurs de validation »try { await Client.create({ nom: 'A', email: 'pas-un-email' });} catch (err) { if (err.name === 'ValidationError') { const details = Object.values(err.errors).map((e) => ({ champ: e.path, message: e.message, })); console.log(details); } else if (err.code === 11000) { console.log('doublon :', err.keyValue); } else { throw err; }}| Erreur | Cause |
|---|---|
ValidationError |
Une règle du schéma n’est pas respectée |
CastError |
Valeur non convertible dans le type attendu |
MongoServerError avec code: 11000 |
Violation d’un index unique |
Validation sans enregistrement :
const client = new Client({ nom: '', email: 'x' });const erreur = client.validateSync();console.log(erreur?.errors);L’unicité n’est pas une validation
Section intitulée « L’unicité n’est pas une validation »unique: true crée un index unique dans MongoDB, il ne valide pas avant l’écriture : un doublon provoque l’erreur E11000 à l’enregistrement. L’index doit exister (voir Performances et production) ; tant qu’il n’est pas créé, les doublons passent. Traiter err.code === 11000 pour renvoyer une réponse claire (409).
Validation lors des mises à jour
Section intitulée « Validation lors des mises à jour »await Client.updateOne({ _id }, { $set: { age: -5 } }, { runValidators: true });Sans runValidators: true, updateOne et findOneAndUpdate ne valident pas. Avec cette option, certaines validations (comme required sur un champ absent de la mise à jour) ne s’appliquent qu’aux champs modifiés.
Hooks (middlewares de document)
Section intitulée « Hooks (middlewares de document) »Des fonctions exécutées avant (pre) ou après (post) une opération :
import bcrypt from 'bcrypt';
schemaClient.pre('save', async function () { if (this.isModified('motDePasse')) { this.motDePasse = await bcrypt.hash(this.motDePasse, 12); }});
schemaClient.post('save', function (doc) { console.log('client enregistré :', doc._id);});
schemaCommande.pre('validate', function () { this.totalCentimes = this.lignes.reduce( (s, l) => s + l.quantite * l.prixCentimes, 0, );});| Hook | Déclenché par |
|---|---|
validate |
validate() et avant save |
save |
save(), create() |
updateOne, findOneAndUpdate, deleteOne… (hooks de requête) |
Les opérations correspondantes |
find, findOne |
Lectures |
aggregate |
Agrégations |
Exemple de hook de requête : exclure par défaut les documents « supprimés logiquement » :
schemaClient.pre(/^find/, function () { this.where({ supprimeLe: null });});Règles :
- Utiliser
function(pas de fonction fléchée) :thisdésigne le document ou la requête. - Les fonctions
asyncsont prises en charge ; ne pas combiner avecnextsauf besoin. Model.updateOne,insertMany,bulkWritene déclenchent pas les hooks de documentsave.
Hachage des mots de passe
Section intitulée « Hachage des mots de passe »npm install bcryptschemaClient.add({ motDePasse: { type: String, required: true, select: false },});
schemaClient.methods.verifierMotDePasse = function (candidat) { return bcrypt.compare(candidat, this.motDePasse);};
const client = await Client.findOne({ email }).select('+motDePasse');const valide = client && (await client.verifierMotDePasse(motDePasseSaisi));Ne jamais stocker un mot de passe en clair. select: false l’exclut des lectures ; select('+motDePasse') l’inclut explicitement.
Gestion des erreurs dans Express
Section intitulée « Gestion des erreurs dans Express »app.use((err, req, res, next) => { if (err.name === 'ValidationError') { return res.status(400).json({ erreur: 'données invalides', details: Object.values(err.errors).map((e) => ({ champ: e.path, message: e.message, })), }); } if (err.code === 11000) { return res.status(409).json({ erreur: 'valeur déjà utilisée', champs: Object.keys(err.keyValue), }); } if (err.name === 'CastError') { return res .status(400) .json({ erreur: `identifiant invalide : ${err.value}` }); } console.error(err); res.status(500).json({ erreur: 'erreur interne' });});Avec Express 5, une erreur levée dans un gestionnaire async atteint ce gestionnaire automatiquement (voir Express : middlewares).
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| La validation ne s’applique pas à une mise à jour | runValidators: true |
Doublons acceptés malgré unique |
Index non créé : Client.syncIndexes() ou createIndexes() ; données existantes en doublon |
Le hook pre('save') ne s’exécute pas |
Mise à jour par updateOne ou insertMany : utiliser save() |
Cannot read properties of undefined dans un hook |
Fonction fléchée : utiliser function |
ValidatorError sans message lisible |
Ajouter un message dans la définition du champ |
| Mot de passe renvoyé dans le JSON | select: false et suppression dans toJSON |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Validation, Middleware
- Suite : Relations et agrégation