Aller au contenu

Mongoose : schémas et modèles

Un schéma décrit la forme des documents d’une collection ; un modèle est l’objet qui permet de les lire et de les écrire.

src/models/client.js :

import mongoose from 'mongoose';
const schemaClient = new mongoose.Schema(
{
nom: { type: String, required: true, trim: true, maxlength: 100 },
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
},
age: { type: Number, min: 0, max: 130 },
actif: { type: Boolean, default: true },
tags: [String],
adresse: {
rue: String,
ville: String,
codePostal: { type: String, match: /^\d{5}$/ },
},
},
{ timestamps: true },
);
export const Client = mongoose.model('Client', schemaClient);

Le modèle Client correspond à la collection clients (nom au pluriel, en minuscules).

Option de schéma Effet
timestamps: true Ajoute et maintient createdAt et updatedAt
collection: 'clients' Impose le nom de la collection
versionKey: false Supprime le champ __v
strict (défaut) Ignore les champs non déclarés à l’enregistrement
Type Exemple
String nom: String
Number age: Number
Boolean actif: Boolean
Date naissance: Date
Buffer fichier: Buffer
ObjectId client: { type: mongoose.Schema.Types.ObjectId, ref: 'Client' }
Decimal128 Montants à précision exacte
Mixed Tout (mongoose.Schema.Types.Mixed) : à éviter, sans validation ni suivi des modifications
Tableau tags: [String], lignes: [schemaLigne]
Carte meta: { type: Map, of: String }

Pour les montants, éviter Number (virgule flottante) : utiliser des centimes entiers ou Decimal128.

Option Effet
required Champ obligatoire (required: [true, 'message'])
default Valeur par défaut (valeur ou fonction)
unique Crée un index unique (ce n’est pas une validation : voir Validation et hooks)
index Crée un index
lowercase, uppercase, trim Transformations de chaînes
enum Valeurs autorisées
min, max, minlength, maxlength, match Contraintes
select: false Exclu des lectures par défaut (mot de passe, données sensibles)
immutable: true Non modifiable après création
get, set Fonctions de lecture et d’écriture
const schemaCommande = new mongoose.Schema({
numero: { type: String, required: true, immutable: true },
client: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Client',
required: true,
index: true,
},
statut: {
type: String,
enum: ['nouvelle', 'payee', 'expediee'],
default: 'nouvelle',
},
totalCentimes: { type: Number, required: true, min: 0 },
passeeLe: { type: Date, default: Date.now },
});
const schemaLigne = new mongoose.Schema(
{
article: { type: String, required: true },
quantite: { type: Number, required: true, min: 1 },
prixCentimes: { type: Number, required: true, min: 0 },
},
{ _id: false },
);
const schemaCommande = new mongoose.Schema({
numero: String,
lignes: [schemaLigne],
});

{ _id: false } évite un identifiant pour chaque ligne. Un tableau incorporé convient aux données lues avec le document parent et de taille bornée ; sinon, utiliser une collection séparée liée par référence (voir Relations et agrégation).

Champs calculés, non stockés :

schemaCommande.virtual('totalLignes').get(function () {
return this.lignes.reduce(
(somme, l) => somme + l.quantite * l.prixCentimes,
0,
);
});
schemaCommande.set('toJSON', { virtuals: true });
schemaCommande.set('toObject', { virtuals: true });
schemaClient.methods.nomComplet = function () {
return `${this.nom} <${this.email}>`;
};
schemaClient.statics.parEmail = function (email) {
return this.findOne({ email: email.toLowerCase() });
};
const alice = await Client.parEmail('Alice@exemple.com');
console.log(alice?.nomComplet());

Utiliser des fonctions classiques (function), pas des fonctions fléchées : this doit désigner le document ou le modèle.

schemaClient.set('toJSON', {
versionKey: false,
transform(doc, ret) {
ret.id = ret._id.toString();
delete ret._id;
delete ret.motDePasse;
return ret;
},
});

Ne jamais renvoyer au client des champs sensibles : les exclure par select: false et par toJSON.

src/models/
├── client.js
├── commande.js
└── index.js # export { Client } from './client.js'; ...
Symptôme Piste
La collection a un nom inattendu Pluriel automatique : définir collection
Un champ envoyé n’est pas enregistré Non déclaré dans le schéma (strict)
OverwriteModelError: Cannot overwrite 'Client' model once compiled Modèle défini deux fois : mongoose.models.Client || mongoose.model(...)
this est undefined dans une méthode Fonction fléchée utilisée
Un champ de type Mixed ne se sauvegarde pas après modification doc.markModified('chemin') avant save()
Les virtuels absents du JSON toJSON: { virtuals: true }
Cast to ObjectId failed Valeur qui n’est pas un identifiant valide

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune : les schémas sont indépendants du système.