Développement Web

Modélisation collections et relations Directus : tutoriel pratique 2026

12 min de lecture

📍 Article principal de la série : Directus 2026 : guide pratique.

Avant d’écrire du code frontend, modéliser proprement vos collections Directus économise des semaines de refactor. Ce tutoriel détaille la modélisation d’une boutique e-commerce ouest-africaine type, avec relations, validations, et best practices.

Prérequis

  • Directus en production (voir tutoriel installation).
  • Compréhension des bases SQL.
  • Niveau attendu : intermédiaire.
  • Temps estimé : 1-2 heures.

Pour modéliser des collections Directus en production, vous avez besoin d’une instance Directus 11.x déjà installée et accessible (Coolify recommandé pour le déploiement). Une base PostgreSQL 15+ recommandée pour les fonctions JSON et les contraintes avancées. Un compte admin Directus avec permissions Settings → Data Model. Pour un catalogue produits e-commerce sérieux, comptez 2-3 heures de modélisation initiale et 1 jour de tests d’intégration avant la mise en production.

Étape 1 — Plan du modèle

Pour une marketplace artisanat ouest-africain :

  • Products : produits avec prix, stock, description.
  • Categories : catégories hiérarchiques.
  • Variants : tailles, couleurs (One-to-Many).
  • Brands : marques (Many-to-One).
  • Vendors : vendeurs marketplace.
  • Tags : tags transversaux (Many-to-Many).
  • Reviews : avis clients.

Avant de créer la moindre collection, dessinez le modèle sur papier ou dans dbdiagram.io. Pour un catalogue de cosmétiques à Yopougon, identifiez les entités principales : Products (titre, description, prix, statut), Categories (hiérarchique), Variants (taille, couleur, prix unitaire), Tags (étiquettes libres), Images. Listez les relations : 1 Product appartient à 1 Category, a plusieurs Variants, a plusieurs Tags. Cette planification évite les refontes douloureuses 6 mois plus tard quand votre catalogue compte 500 références.

Étape 2 — Collection Products

Settings → Data Model → Create Collection : products.

Fields :

  • id (UUID auto, primary).
  • status (Select : draft, published, archived).
  • sort (Integer, pour ordering manuel).
  • name (Input, required, unique).
  • slug (Input, required, unique, regex slug).
  • description (WYSIWYG).
  • price (Decimal, required).
  • currency (Select : XOF, MAD, TND, EUR, USD, default XOF).
  • stock (Integer, default 0).
  • images (Files, Multiple).
  • category (Many-to-One → categories).
  • brand (Many-to-One → brands).
  • vendor (Many-to-One → vendors).
  • tags (Many-to-Many → tags via products_tags).
  • variants (One-to-Many → variants).

Étape 3 — Collection Categories (hiérarchique)

Pour catégories type Mode → Femme → Robes → Pagnes :

  • name (Input).
  • slug (Input, unique).
  • parent (Many-to-One → categories, self-reference).
  • children (One-to-Many → categories, alias).
  • icon (File).
  • sort (Integer).

Créez la collection categories avec les champs id (UUID auto), name (string requis), slug (string unique), parent_id (M2O vers categories elle-même). Cette auto-référence permet la hiérarchie multi-niveaux (catégorie → sous-catégorie → sous-sous-catégorie). Activez Tree View dans les settings de la collection pour avoir l’interface arborescente dans l’admin Directus. Limitez la profondeur à 3 niveaux pour éviter la complexité utilisateur — au-delà, créez plutôt des tags pour les axes secondaires.

Étape 4 — Collection Variants

Pour produits avec tailles/couleurs :

  • product (Many-to-One → products).
  • size (Select : XS, S, M, L, XL, XXL).
  • color (Input).
  • price_adjustment (Decimal, default 0).
  • stock (Integer).
  • sku (Input, unique).

Créez la collection product_variants avec product_id (M2O products), sku (string unique), price (decimal 10,2), stock_quantity (integer), color (string), size (enum). Cette structure dénormalisée évite les jointures complexes pour les requêtes fréquentes. Activez l’index sur product_id pour les performances. Pour gérer les images spécifiques par variante (variantes de couleur avec photos différentes), ajoutez un champ image (file). Cette modélisation couvre 90 % des cas e-commerce.

Étape 5 — Many-to-Many : products_tags

Directus crée automatiquement la junction table. Sur products → field tags → Many to Many → choisir collection tags. Directus génère products_tags avec FK products_id et tags_id.

Pour la relation M2M entre Products et Tags, créez une junction collection products_tags avec product_id et tag_id (les deux M2O). Directus génère automatiquement l’interface tags multi-select dans le formulaire produit. Pour éviter les doublons, créez un index unique composite sur (product_id, tag_id). Les tags facilitent la navigation par filtres (bio, vegan, made-in-Senegal) sans contraindre l’arborescence catégorielle qui reste rigide.

Étape 6 — Champs multilingues (translations)

Pour Maroc/Tunisie/Algérie servant FR + AR :

  1. Field type : Translations.
  2. Languages : FR, AR, EN.
  3. Fields traduits : name, description, slug.

Directus crée auto une collection products_translations avec FK product_id et locale.

Directus 11 propose nativement le pattern translations via une collection séparée products_translations avec product_id, language (string fr/en/wo) et les champs traduits (title, description). L’interface admin affiche un sélecteur de langue qui filtre l’édition. Pour un site bilingue français-arabe à Dakar, déclarez les langues dans Settings → Languages. L’API REST/GraphQL retourne automatiquement la langue demandée via le query param ?fields=*,translations.* ou via le header Accept-Language.

Étape 7 — Validations métier

Field price : Validation type → Number → min: 0. Custom : ne pas accepter price = 0 si status = published.

Slug : Regex ^[a-z0-9-]+$. Unique.

Stock : > 0 si status = published.

Directus permet des validations au niveau du champ et au niveau de la collection. Sur le champ price : Validation Rules → required, min: 0, max: 1000000. Sur le champ slug : pattern regex /^[a-z0-9-]+$/. Pour des règles complexes (le stock_quantity doit baisser de moins de 50 % en une mise à jour), utilisez les Flows Directus avec un Operation Webhook qui valide avant le PUT. Cette discipline empêche les données aberrantes qui polluent les statistiques.

Étape 8 — Computed fields

Champ « display_price » qui formate prix avec currency :

// Field type: Custom (formula)
{{ price | format_currency(currency) }}

Les computed fields se calculent automatiquement depuis d’autres champs. Sur la collection products, créez total_stock comme somme des stock_quantity de toutes les variants. Sur products, available comme boolean qui vaut true si total_stock supérieur à 0 et statut = published. Ces calculs s’effectuent côté serveur via des Flows ou des hooks de la base. Pour les frontends (Next.js, Astro), exposer ces champs via l’API évite de recalculer côté client.

Étape 9 — Permissions par champ

Role Editor : peut update name, description, price, images. Pas update status (Reviewer only). Pas delete.

Directus offre des permissions granulaires : un rôle Editor peut lire le champ cost_price mais pas le modifier, un rôle Public ne le voit même pas. Configurez dans Settings → Roles → Editor → Collection products → Field-level permissions. Pour un catalogue B2B où le prix d’achat doit rester confidentiel mais où les éditeurs métadonnées peuvent voir le prix de vente, cette précision est non négociable. Sans elle, vous risquez la fuite d’informations sensibles via l’API publique.

Étape 10 — Test API

curl https://cms.votre-entreprise.com/items/products?fields=*,category.name,brand.name,images.*&filter[status][_eq]=published
# Retourne produits publiés avec catégorie + marque + images peuplées

Validez le modèle via l’API REST Directus. curl https://api.example.com/items/products?filter[status][_eq]=published&limit=5 retourne les 5 premiers produits publiés. Testez aussi GraphQL via /graphql. Vérifiez que les relations sont expandables : ?fields=*,category.name,variants.*,tags.tag_id.name. Cette étape révèle les modélisations bancales (relations mal nommées, foreign keys manquantes). Documentez les requêtes courantes dans un Postman collection partagé avec l’équipe frontend.

Erreurs fréquentes

Erreur Cause Solution
M2M ne fonctionne pas Junction table mal créée Recréer field via Studio
Translations vides Default language pas sauvegardé Toujours fournir version par défaut
Slug doublons Pas unique constraint Activer unique au niveau field
Image upload échoue Storage S3 mal configuré Voir tutoriel installation
Performance N+1 Pas de fields populate ?fields=*,relation.*
Sort manuel ne marche pas Field sort non créé Ajouter Integer sort

Ce que la théorie européenne oublie en zone CEDEAO

Trois précisions. Multi-currency : produits en XOF (Sénégal/CI), MAD (Maroc), TND (Tunisie). Champ currency par produit + display formatting frontend. Vendeurs marketplace : vendor as collection séparée + Many-to-One product → vendor permet marketplace multi-vendeur. Tags transverses : « bio », « artisanal », « fait main » comme tags M2M, filtres puissants côté API.

Tutoriels frères

Cette modélisation Directus se complète bien avec d’autres briques. Astro ou Next.js consomment l’API GraphQL pour générer le frontend statique ou SSR. Algolia ou Meilisearch indexent les products pour la recherche full-text. Stripe ou Wave + Mixx by Yas gèrent les paiements (Directus stocke juste l’order, pas le paiement directement). Cette stack cohérente forme un e-commerce headless moderne et maîtrisé.

FAQ

Migrations schema en prod ? Studio génère migrations Knex.js. Apply via npx directus database migrate:latest.

Modifier collection sans Studio ? Direct SQL puis Studio refresh. Mais préférable via Studio pour cohérence.

Performance avec 1M items ? Indexes sur foreign keys + status + slug. Pagination 25 items/page.

Schema export ? npx directus schema snapshot → JSON portable.

Versioning content ? Directus 11+ a Content Versioning (revisions).

Sur un angle proche

Pour aller plus loin avec Directus, voyez nos tutoriels d’intégration Next.js avec Directus, déploiement Coolify, configuration des Flows pour automation, et permissions multi-tenants. La documentation officielle docs.directus.io reste la référence canonique avec des exemples GraphQL complets. Le Discord Directus rassemble une communauté active où les mainteneurs répondent aux questions techniques en quelques heures.

Étape 1 — Préparer l’environnement Directus 11 sur un VPS Dakar

Avant de modéliser, on s’assure de tourner sur Directus 11.x (cycle stable janvier 2026), Node 22 LTS et PostgreSQL 16. Sur un VPS 2 vCPU / 4 Go RAM hébergé à Paris ou Lagos (latence ~50 ms vers Dakar et Abidjan), c’est largement suffisant pour les 5 000 premières lignes d’une app SaaS.

docker run -d --name directus -p 8055:8055 \
  -e KEY=255d861b-5ea1 -e SECRET=6116487b-cda1 \
  -e ADMIN_EMAIL=admin@example.sn -e ADMIN_PASSWORD=changeme \
  -e DB_CLIENT=pg -e DB_HOST=db -e DB_PORT=5432 \
  -e DB_DATABASE=directus -e DB_USER=directus -e DB_PASSWORD=secret \
  directus/directus:11

Une fois lancé, ouvrez http://VOTRE_IP:8055. Vous devez voir l’écran de login et pouvoir entrer avec ADMIN_EMAIL/ADMIN_PASSWORD. Si la page reste blanche, vérifiez que PostgreSQL répond avec docker logs directus | grep -i error.

Étape 2 — Créer la collection racine « clients »

Dans Settings → Data Model → Create Collection, on commence par la table parent du domaine : clients. Cochez « UUID » comme primary key (compatible avec une réplication multi-région future entre Dakar et Abidjan), désactivez « Singleton », activez les champs système date_created, user_created, status.

Ajoutez ensuite : raison_sociale (string, requis), ninea (string, 9 chiffres, regex ^[0-9]{9}$) pour le Sénégal, rccm (string) pour la Côte d’Ivoire, telephone (string), devise (dropdown : XOF, EUR, USD, par défaut XOF). Le rappel : 1 EUR = 655,957 FCFA, fixe depuis 1999.

Étape 3 — Modéliser une relation Many-to-One (factures → clients)

Créez la collection factures. Dans le champ ajouter, choisissez l’interface « Many to One » et sélectionnez clients comme collection liée. Directus crée automatiquement la colonne client (FK UUID) et la contrainte ON DELETE SET NULL par défaut. Pour de la facturation comptable, basculez sur RESTRICT via le bouton avancé : on ne supprime jamais un client qui a des factures.

# Vérification SQL côté Postgres
\d factures
# Doit afficher : client uuid REFERENCES clients(id) ON DELETE RESTRICT

Côté UI, ajoutez le champ numero (string, unique), montant_ht (decimal 12,2), tva_taux (decimal, défaut 18 pour le Sénégal et la Côte d’Ivoire), date_emission (date).

Étape 4 — Modéliser une relation One-to-Many (client → contacts)

Sur la collection clients, ajoutez un champ « One to Many » nommé contacts qui pointe vers une nouvelle collection client_contacts. Directus génère la table fille avec la FK client automatiquement. Renseignez nom, prenom, email (validation regex), fonction (dropdown : DG, DAF, Comptable, Acheteur), whatsapp (string, format international +221, +225).

Dans la fiche client, le panneau O2M affiche désormais une table inline « Contacts » avec bouton « Create New ». Testez en ajoutant 2 contacts à un client : ils doivent apparaître côté client_contacts avec la bonne FK.

Étape 5 — Modéliser une relation Many-to-Many (factures ↔ produits)

Créez produits (libelle, prix_ht, unite). Sur factures, ajoutez un champ « Many to Many » vers produits. Directus propose de créer la table de jonction factures_produits. Acceptez, puis enrichissez-la après création avec : quantite (decimal), remise_pct (decimal 0-100), montant_ligne (decimal, calculé via hook).

Cette table de jonction enrichie devient un véritable « ligne de facture » réutilisable. C’est le pattern recommandé pour de la facturation BTOB ouest-africaine où les remises se négocient ligne par ligne.

Étape 6 — Permissions par rôle (admin, comptable, commercial)

Settings → Roles & Permissions. Créez 3 rôles : Admin (tout), Comptable (CRUD factures, lecture clients), Commercial (CRUD clients/contacts, lecture factures, pas de suppression). Pour chaque collection, cliquez sur la cellule croisée (Create / Read / Update / Delete) et basculez « All Access » ou « Custom » pour des règles fines via filtre JSON.

// Exemple filtre : un commercial ne voit que ses propres clients
{
  "user_created": { "_eq": "$CURRENT_USER" }
}

Ce filtre s’applique automatiquement à toutes les requêtes API REST et GraphQL : aucune fuite côté front Next.js même si un utilisateur forge une requête.

Étape 7 — Hook Flow pour calculer le total facture

Settings → Flows → Create Flow. Trigger : « Event Hook » sur factures.items.create et factures.items.update. Operation 1 : « Read Data » sur factures_produits filtré par la facture. Operation 2 : « Run Script » qui somme quantite * prix_ht * (1 - remise_pct/100). Operation 3 : « Update Data » sur factures avec montant_ht calculé.

Vérifiez qu’à chaque création de ligne, le champ montant_ht de la facture parente se met à jour en moins de 200 ms côté UI.

Étape 8 — Exposer une vue API filtrée pour le front Next.js

Plutôt que requêter la base brute, on appelle /items/factures?fields=numero,montant_ht,client.raison_sociale,produits.produits_id.libelle&filter[status][_eq]=published. Directus gère la résolution des relations en une seule requête, évite le N+1 et renvoie un JSON propre directement consommable par TanStack Query.

const res = await fetch('https://api.example.sn/items/factures?fields=*,client.raison_sociale', {
  headers: { Authorization: 'Bearer ' + token }
});
// Vérifier res.status === 200 et data.length cohérent

Étape 9 — Sauvegarder le schéma et versionner avec Git

La commande npx directus schema snapshot ./snapshots/2026-05-05.yaml exporte tout le modèle (collections, relations, permissions) dans un fichier YAML. Commitez-le : c’est votre source de vérité. Pour propager vers staging Abidjan, lancez npx directus schema apply ./snapshots/2026-05-05.yaml. Vous obtenez un déploiement reproductible, sans clic dans l’UI.

Dans la continuité sur l’écosystème JavaScript moderne et Astro côté front, consultez Server Islands Astro 5 et Migrer GitHub vers Forgejo.

Partager