Trier par date avec Mongoose : toutes les syntaxes, et laquelle choisir
Mongoose accepte six façons d'écrire le même tri. Elles ne se valent pas toutes en lisibilité — voici la carte complète, et le piège du tri sur un champ non indexé.
· 4 min de lecture · mis à jour le 3 août 2026
Trier des documents par date est probablement la requête la plus banale qu'on écrit avec Mongoose. C'est aussi celle sur laquelle je vois le plus de variations dans une même base de code — parce que l'API en accepte au moins six, toutes valides, et que chacune a survécu à une version différente de la bibliothèque.
Voici la carte complète, puis ce que je retiens en pratique.
Les six écritures équivalentes
Toutes ces requêtes renvoient exactement le même résultat : les articles du plus récent au plus ancien.
// 1. La chaîne préfixée — le "-" signifie décroissant
Article.find({}).sort('-date')
// 2. L'objet numérique — -1 décroissant, 1 croissant
Article.find({}).sort({ date: -1 })
// 3. L'objet avec mot-clé court
Article.find({}).sort({ date: 'desc' })
// 4. L'objet avec mot-clé long
Article.find({}).sort({ date: 'descending' })
// 5. Via le troisième argument de find()
Article.find({}, null, { sort: '-date' })
// 6. Idem, en notation objet
Article.find({}, null, { sort: { date: -1 } })
Les quatre premières passent par le query builder ; les deux dernières exploitent le
paramètre options de find(), dont la signature complète est
find(filter, projection, options).
Ce que je choisis, et pourquoi
.sort({ date: -1 }). C'est la forme que je garde partout, pour trois raisons :
- Elle est identique à celle du shell MongoDB. On peut copier une requête depuis
mongoshvers le code, et inversement, sans traduire. - Elle s'étend naturellement au tri multi-champs, là où la chaîne
'-date title'devient vite illisible. - Elle ne cache pas la direction derrière un tiret qu'on rate à la relecture d'une diff.
La chaîne '-date' reste pratique quand la direction du tri vient d'un paramètre HTTP :
.sort(req.query.sort) accepte directement -date ou date. Attention alors à valider
l'entrée — voir plus bas.
Trier sur plusieurs champs
L'ordre des clés compte : Mongo applique les critères de gauche à droite.
// Les articles épinglés d'abord, puis les plus récents
Article.find({})
.sort({ pinned: -1, date: -1 })
.limit(20)
Ici on obtient les épinglés en tête, chaque groupe étant lui-même ordonné du plus récent au plus ancien.
Le piège : trier sans index
C'est le point que l'article d'origine ne traitait pas, et c'est pourtant celui qui coûte cher en production.
Quand aucun index ne couvre le champ de tri, MongoDB effectue un tri en mémoire, plafonné à 32 Mo (100 Mo sur les versions récentes). Au-delà, la requête n'est pas lente : elle échoue.
Executor error during find command :: caused by ::
Sort exceeded memory limit of 33554432 bytes
La parade tient en une ligne, dans le schéma :
const articleSchema = new mongoose.Schema({
title: String,
date: { type: Date, default: Date.now, index: true },
});
// Pour un tri composé, l'index doit l'être aussi
articleSchema.index({ pinned: -1, date: -1 });
Un index B-tree est déjà ordonné : le tri devient une simple lecture séquentielle. On peut le
vérifier — IXSCAN en tête de plan, et surtout pas d'étape SORT :
const plan = await Article.find({}).sort({ date: -1 }).explain('executionStats');
console.log(plan.executionStats.executionStages.stage); // IXSCAN, pas SORT
Pagination : skip ne passe pas à l'échelle
Le réflexe .skip(page * 20).limit(20) fonctionne jusqu'à ce qu'il ne fonctionne plus :
MongoDB doit parcourir et jeter tous les documents sautés. À la page 500, on lit 10 000
documents pour en renvoyer 20.
La pagination par curseur s'appuie sur le même index que le tri :
// Page suivante : tout ce qui est strictement antérieur au dernier élément vu
const filter = cursor ? { date: { $lt: new Date(cursor) } } : {};
const page = await Article.find(filter).sort({ date: -1 }).limit(20);
const nextCursor = page.at(-1)?.date.toISOString() ?? null;
Coût constant quelle que soit la profondeur. Si les dates peuvent être identiques, ajoutez
_id comme départage — sort({ date: -1, _id: -1 }) avec un filtre composé — sinon des
documents sautent d'une page à l'autre.
Si le tri vient du client
.sort() accepte une chaîne arbitraire. Laisser passer req.query.sort tel quel donne au
client la main sur n'importe quel champ, y compris ceux non indexés — donc un tri en mémoire
déclenchable à distance.
const SORTABLE = new Set(['date', '-date', 'title', '-title']);
const sort = SORTABLE.has(req.query.sort) ? req.query.sort : '-date';
Article.find({}).sort(sort);
En résumé
- Six syntaxes, une seule à retenir :
.sort({ date: -1 }). - L'ordre des clés définit la priorité en tri multi-champs.
- Indexez le champ de tri — sinon tri en mémoire, plafonné, et échec en production.
- Préférez la pagination par curseur à
skipdès que les volumes grossissent. - Validez tout critère de tri qui vient du client.
Version réécrite et étendue d'un article publié le 24 août 2016 sur Medium.
Une première version de cet article a été publiée sur Medium.