À quoi ça sert

Une base relationnelle (Postgres, SQLite) structure les données en tables avec colonnes typées. C'est parfait quand le schéma est stable et qu'on a beaucoup de relations entre objets. Mais ça devient pénible quand :

  • Chaque enregistrement a des champs différents (ex : journaux d'événements hétérogènes, configurations utilisateur).
  • Le schéma évolue souvent et qu'on veut éviter une migration pour chaque ajout de champ.
  • On manipule des structures imbriquées (objets dans objets, listes d'objets) qui demanderaient plein de tables jointes en SQL.

MongoDB stocke un document (en gros, un dict Python / un objet JSON) dans une collection (équivalent d'une table, mais sans schéma figé). On y stocke directement la structure qu'on a en mémoire, sans découper ni recoller.

MongoDB vs PostgreSQL : la grande question

En 2026, Postgres a rattrapé l'essentiel grâce au type JSONB : tu peux stocker du JSON imbriqué, le requêter, l'indexer. Du coup, le besoin pour MongoDB s'est réduit. Mongo reste pertinent pour : énormément de documents hétérogènes (logs d'événements à grande échelle), équipes habituées à Mongo, ou scaling horizontal facile (sharding natif). Pour un projet MLOps classique, Postgres est plus simple — Mongo a sa place sur des cas spécifiques.

Un exemple d'usage

Tu stockes les événements d'utilisation d'une application : chaque event a un type, un user, un timestamp, et un payload qui dépend du type. Schéma figé impossible — Mongo est confortable :

python
from pymongo import MongoClient
from datetime import datetime, timezone

client = MongoClient("mongodb://localhost:27017")
db = client["analytics"]
events = db["events"]

# Insérer des documents — pas besoin de CREATE TABLE
events.insert_one({
    "user_id": "u_42",
    "type": "login",
    "ts": datetime.now(timezone.utc),
    "ip": "203.0.113.7",
})
events.insert_one({
    "user_id": "u_42",
    "type": "purchase",
    "ts": datetime.now(timezone.utc),
    "items": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 1}],
    "total": 42.50,
})

# Requêter : toutes les actions de u_42 dans les dernières 24h
from datetime import timedelta
recent = events.find({
    "user_id": "u_42",
    "ts": {"$gte": datetime.now(timezone.utc) - timedelta(days=1)},
})
for evt in recent:
    print(evt)

Les deux documents n'ont pas les mêmes champs (ip vs items + total) — c'est attendu en Mongo, là où Postgres demanderait soit un JSONB soit deux tables séparées.

How-to : installer et utiliser MongoDB

  1. Lancer un MongoDB en local (Docker)

    Comme pour PostgreSQL, l'image officielle Docker est la voie royale :

    bash
    docker run -d --name mongo \
        -e MONGO_INITDB_ROOT_USERNAME=admin \
        -e MONGO_INITDB_ROOT_PASSWORD=secret \
        -p 27017:27017 \
        -v mongodata:/data/db \
        mongo:8

    Volume monté : indispensable pour persister les données entre redémarrages du conteneur.

  2. Installer le client Python

    pymongo est le driver officiel. Avec UV :

    bash
    uv add pymongo

    Pour de l'async (FastAPI…), il y a motor qui expose la même API en asynchrone.

  3. Se connecter

    python
    from pymongo import MongoClient
    
    # URL complète (format MongoDB)
    client = MongoClient("mongodb://admin:secret@localhost:27017")
    
    # Hiérarchie : client > database > collection
    db = client["projet_devia"]
    users = db["users"]
    
    # Ping pour vérifier que ça répond
    print(client.server_info()["version"])

    Note : Mongo crée la database et la collection au premier insert. Pas besoin d'un CREATE DATABASE.

  4. Insérer des documents

    python
    # Un document = un dict Python
    users.insert_one({"name": "Alice", "age": 30, "tags": ["admin"]})
    
    # Insertion en lot (plus rapide)
    users.insert_many([
        {"name": "Bob", "age": 25},
        {"name": "Carol", "age": 35, "tags": ["member"]},
    ])

    Mongo ajoute automatiquement un champ _id (un ObjectId unique) à chaque document. C'est l'équivalent PRIMARY KEY SQL.

  5. Requêter

    Les requêtes Mongo sont elles-mêmes des dicts. Les opérateurs commencent par $ :

    python
    # Un seul document
    users.find_one({"name": "Alice"})
    
    # Plusieurs (renvoie un curseur, on l'itère)
    for u in users.find({"age": {"$gte": 30}}):
        print(u)
    
    # Opérateurs de comparaison
    users.find({"age": {"$gt": 18, "$lt": 65}})
    
    # Recherche dans un tableau
    users.find({"tags": "admin"})
    
    # Projection : ne ramener que certains champs
    users.find({}, {"name": 1, "_id": 0})
    
    # Tri / limite
    users.find().sort("age", -1).limit(5)
  6. Mettre à jour, supprimer

    python
    # Update : opérateurs $set, $inc, $push…
    users.update_one(
        {"name": "Alice"},
        {"$set": {"age": 31}, "$push": {"tags": "vip"}}
    )
    
    # Upsert : insère si pas trouvé, sinon update
    users.update_one(
        {"name": "Dave"},
        {"$set": {"age": 40}},
        upsert=True,
    )
    
    # Suppression
    users.delete_one({"name": "Bob"})
    users.delete_many({"age": {"$lt": 18}})
  7. Agréger (pipeline)

    L'équivalent du GROUP BY SQL en Mongo se fait via le aggregation framework — un pipeline d'étapes :

    python
    pipeline = [
        {"$match": {"type": "purchase"}},
        {"$group": {
            "_id": "$user_id",
            "total": {"$sum": "$total"},
            "n_purchases": {"$sum": 1},
        }},
        {"$sort": {"total": -1}},
        {"$limit": 10},
    ]
    for row in events.aggregate(pipeline):
        print(row)
  8. Créer des index

    Comme en SQL, un index accélère les requêtes répétées sur un champ. À créer une seule fois :

    python
    users.create_index("name")                  # asc
    users.create_index([("age", -1)])             # desc
    users.create_index("email", unique=True)     # unicité
    Penser aux index dès le début

    Sans index, une requête sur 1M de documents fait un scan complet (lent). Pour chaque champ qui sert régulièrement de filtre, ajoute un index — même règle qu'en PostgreSQL.

Aide-mémoire

python (connexion)
from pymongo import MongoClient
client = MongoClient("mongodb://user:pwd@host:27017")
col = client["db"]["collection"]
python (CRUD)
col.insert_one({...}); col.insert_many([{...}, …])
col.find_one({"k": "v"}); col.find({...})
col.update_one({...}, {"$set": {...}})
col.delete_one({...}); col.delete_many({...})
python (opérateurs courants)
{"age": {"$gte": 18, "$lt": 65}}
{"tags": {"$in": ["a", "b"]}}
{"name": {"$regex": "^Al"}}
{"$and": [{...}, {...}]}; {"$or": [{...}, {...}]}
python (aggregation)
col.aggregate([
    {"$match": {...}},
    {"$group": {"_id": "$field", "n": {"$sum": 1}}},
    {"$sort": {"n": -1}},
])

MongoDB et le reste de l'écosystème

  • PostgreSQL — le choix « par défaut » concurrent. Postgres + JSONB couvre 80% des cas où on aurait pensé à Mongo, avec en bonus le SQL et les contraintes d'intégrité. Mongo gagne en simplicité d'usage quand les documents sont vraiment hétérogènes et nombreux.
  • Docker — l'image officielle mongo tourne partout, avec un docker-compose.yml à côté du backend.
  • FastAPI — combo cohérent : pymongo (sync) ou motor (async) pour parler à Mongo, Pydantic pour valider les documents entrants/sortants, FastAPI pour le routing.
  • Pydantic — même en Mongo « sans schéma », on met de facto un schéma côté application avec Pydantic. La lib beanie (ODM) fait ça nativement, à la manière d'un SQLAlchemy pour Mongo.
  • Prometheus / Grafana — Mongo expose des métriques que Prometheus peut scraper via un exporter, avec des dashboards Grafana tout faits.
  • Redis — souvent associé à Mongo en cache : Mongo persiste, Redis cache les requêtes chaudes (latence sub-milliseconde).
Quand choisir Mongo plutôt que Postgres ?

Trois bonnes raisons : (1) tes documents sont vraiment hétérogènes et tu en as beaucoup (event logs, contenus d'apps mobiles…). (2) tu prévois un scaling horizontal massif (sharding natif Mongo est plus simple que celui de Postgres). (3) ton équipe maîtrise déjà Mongo. Sinon, prends Postgres — le JSONB règle la plupart des « besoins NoSQL » sans changer de stack.

Pour aller plus loin