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.
Premier modèle
Section intitulée « Premier modèle »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.
Options de champ
Section intitulée « Options de champ »| 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 },});Sous-documents et tableaux
Section intitulée « Sous-documents et tableaux »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).
Virtuels
Section intitulée « Virtuels »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 });Méthodes et statiques
Section intitulée « Méthodes et statiques »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.
Contrôler le JSON renvoyé
Section intitulée « Contrôler le JSON renvoyé »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.
Un fichier par modèle
Section intitulée « Un fichier par modèle »src/models/├── client.js├── commande.js└── index.js # export { Client } from './client.js'; ...Dépannage
Section intitulée « Dépannage »| 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.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Schémas, Types
- Suite : CRUD et requêtes