Table of Contents
- Introduction : Le backend est désormais une conversation
- Qu'est-ce que cette « compétence Directus »
- Prérequis : ce dont vous avez besoin avant de commencer
- Installation de la compétence dans Hermes Agent
- Anatomie d'une spécification backend à invite unique
- Exemple 1 : Backend de blog (prêt pour le multilingue)
- Exemple 2 : Backend de commerce électronique en une seule invite
- Exemple 3 : CRM simple (Contacts + Entreprises + Interactions)
- Comment ça marche réellement : la sauce secrète de la compétence
- Modèles éprouvés en production que nous avons découverts
- 1. Le schéma et la méta sont obligatoires
- 2. Utilisez get_expanded() pour les relations
- 3. Les identifiants sont des chaînes dans GraphQL, des entiers dans REST
- 4. Les écritures de la table de jonction passent par la collection de jonctions
- 5. Les collections de traduction nécessitent des insertions en deux étapes
- 6. Le bug de troncature POST et le correctif PATCH
- 7. Vérifiez l'accès en écriture avant de publier
- Gestion des erreurs : qu'est-ce qui ne va pas et comment y remédier
- Modèles avancés : filtres, relations et champs d'énumération
- L'API Admin : lorsque vous devez toucher au schéma par programmation
- Rassembler tout cela : votre première session Vibe-Code
- Ce que cela permet : un prototypage rapide à grande échelle
- Limitations et pièges
- Conclusion : le backend est désormais une conversation
- Prochaines étapes
Introduction : Le backend est désormais une conversation
!Vibe-coding workflow diagram: natural language prompt to Directus schema to production API
Voici un schéma frustrant que la plupart des développeurs connaissent bien :
- Vous avez une brillante idée d’application
- Vous commencez à échafauder la base de données - « utilisateurs », « publications », « commandes »
- Vous créez la couche API - points de terminaison REST, résolveurs GraphQL
- Vous câblez l'authentification, les autorisations et la validation
- Trois jours plus tard, vous arrivez enfin à la fonctionnalité que vous vouliez créer
Cette friction est réelle. Et en 2026, c'est mort.
Et si vous pouviez sauter complètement les étapes 2 à 4 ? Et si vous pouviez décrire l'intégralité de votre backend en anglais simple, appuyer sur Entrée et disposer d'un schéma, d'une API et d'un panneau d'administration prêts à l'emploi ?
C'est ce que signifie codage vibratoire d'un backend. Et avec Directus - le CMS open source sans tête qui transforme votre base de données en une API instantanée - associé à la compétence Nikola66 Directus (un client GraphQL universel pour les agents IA), il est désormais possible d'échafauder une couche de données entière en une seule fois.
Ce n'est pas une démo de jouet. Il s'agit d'un véritable workflow que nous utilisons chez Ainex pour faire tourner les backends du projet en quelques minutes, et non en quelques jours. Dans ce guide, vous apprendrez :
- Ce que fait réellement la compétence Directus sous le capot
- Comment l'installer et vous connecter à votre instance Directus
- L'anatomie d'une spécification backend à invite unique
- Exemples étape par étape : création d'un blog, d'une boutique e-commerce et d'un CRM
- Modèles de production, gestion des erreurs et pièges que nous avons déjà rencontrés dans la nature
- Comment vérifier que votre backend nouvellement échafaudé est actif et interrogeable
Allons-y.
Qu'est-ce que cette « compétence Directus » ?
La compétence de nikola66/directus-skill est un client Python qui parle directement les API GraphQL et REST de Directus. Il est conçu pour les agents IA comme Hermes, mais vous pouvez également l'utiliser comme bibliothèque autonome.
Propriétés clés qui rendent le vibe-coding possible :
- Indépendant de la collection : peu importe si vous travaillez avec
Blog_PostsouEcom_Products. Les mêmes méthodes fonctionnent sur n’importe quelle collection. - Surface CRUD complète :
get,insert,update,delete,countet mêmecreate_collectionetcreate_fieldpour les opérations de schéma (administrateur uniquement). - Extension de relation intelligente : utilisez la notation par points comme
author.nameouorder_items.product.titleet la compétence crée les ensembles de sélection GraphQL imbriqués corrects. - Opérateurs de filtres : tous les filtres Directus pris en charge -
_eq,_contains,_in,_gt,_null, etc. - Gestion des erreurs : déclenche
DirectusErroravec des détails structurés afin que vous sachiez exactement pourquoi quelque chose a échoué.
Lorsque vous chargez cette compétence dans un agent IA, vous donnez à cet agent la possibilité de démarrer un schéma de base de données complet via une conversation.
Prérequis : ce dont vous avez besoin avant de commencer
Avant de pouvoir vibe-coder un backend, vous avez besoin de trois choses :
- Une instance Directus - auto-hébergée ou Directus Cloud. Nous courons sur
https://hub.aratech.ae. - Un jeton API avec les autorisations appropriées. Pour la création de schéma, vous aurez besoin d'un jeton d'administrateur. Obtenez-le depuis Directus → Paramètres → Jetons API → Créer (portée administrateur, toutes les autorisations).
- Python 3.9+ avec la compétence installée :
# Option A : Installer depuis PyPI (une fois publié)
pip installer directus-compétence
## Option B : Cloner et utiliser directement (actuel)
clone git https://github.com/nikola66/directus-skill.git
## Copiez le package dans votre projet ou ajoutez-le à sys.pathDepuis avril 2026, la compétence est activement maintenue et validée en production par rapport à une instance Directus 10+ avec un CRUD complet, des opérateurs de filtrage et une gestion de collection fonctionnant de manière fiable.
Installation de la compétence dans Hermes Agent
Si vous exécutez Hermes Agent (notre plateforme d'orchestration multi-agents), l'ajout de la compétence s'effectue en une seule commande :
compétences hermes installer directusOu pour ajouter directement la version amont de GitHub :
les compétences npx ajoutent nikola66/directus-skillUne fois installée, la compétence « directus » est chargée automatiquement lorsque vous posez des questions sur les opérations Directus. Vous n’avez pas besoin d’importer quoi que ce soit manuellement – l’agent s’en charge.
Définissez vos variables d'environnement (nous les conservons dans ~/.bashrc ou dans la session passerelle) :
exporter DIRECTUS_URL="https://hub.aratech.ae"
export DIRECTUS_API_TOKEN="user_token_here" # opérations CRUD
export DIRECTUS_ADMIN_TOKEN="admin_token_here" # Gestion des schémasRemarque : Dans la passerelle Hermes, ceux-ci sont injectés automatiquement depuis la config de la passerelle. Si vous exécutez des scripts autonomes, définissez-les vous-même.
Anatomie d'une spécification backend à invite unique
La magie se produit lorsque vous décrivez l’intégralité de votre modèle de données dans une seule invite en langage naturel. Voici le modèle :
Construisez-moi un backend [TYPE] avec Directus. Créez ces collections :
1. [Nom de la collection] - [objectif]
- nom_champ (type), champ2 (type), ...
- relations : [détails de la relation]
2. [Nom de la collection] - ...
...
Après avoir créé toutes les collections :
- Vérifiez en appelant get_collections() et en imprimant la liste
- Seed [N] enregistrements d'échantillons pour chaque collection
- Afficher le script Python completL'agent interprète cela, le traduit en appels d'API d'administration Directus (POST /collections avec des définitions de champs complètes), l'exécute et renvoie un schéma fonctionnel.
La compétence fait le gros du travail :
- Construit la structure de charge utile correcte pour les exigences originales du « schéma » et des « méta » de Directus
- Appelle
create_collection()par collection avec la liste complète des champs - En option, amorcez des exemples de données via des opérations CRUD
- Vérifie le succès avec
get_collections()
Exécution tout en un, sans intervention manuelle.
Exemple 1 : Backend de blog (prêt pour le multilingue)
Commençons par un classique : un blog prenant en charge 5 langues. Voici l'invite complète que vous donneriez à l'agent :
Construisez-moi un backend de blog multilingue avec Directus. Créez ces collections :
1. Blog_Authors - profils d'auteurs
- nom (chaîne), slug (chaîne, unique), bio (texte), avatar (fichier), social_twitter (chaîne, nullable)
- statut : brouillon/publié
- Champs : id, create_at, update_at
2. Blog_Categories - taxonomie hiérarchique
- nom (chaîne), slug (chaîne, unique), description (texte), catégorie_parent (plusieurs-à-un → Blog_Categories, nullable)
- icône (chaîne, nom de l'icône), couleur (chaîne, hexadécimal)
- Champs : id, sort_order
3. Blog_Tags - balises plates
- nom (chaîne), slug (chaîne, unique), couleur (chaîne, hexadécimal)
- Champs : identifiant
4. Blog_Posts - enregistrements parents (métadonnées uniquement, selon notre conception de schéma)
- statut (brouillon/publié), publié_at (dateheure, nullable)
- auteur (plusieurs-à-un → Blog_Authors), catégorie (plusieurs-à-un → Blog_Categories)
- feature_image (fichier), Reading_time_minutes (entier)
- is_featured (booléen), is_pillar (booléen)
- Champs : id, create_at, update_at
5. Blog_Posts_Translations - contenu par langue (enregistrements enfants)
- blog_posts_id (entier FK → Blog_Posts), langues_code (chaîne : en/ar/de/es/fr)
- titre (chaîne), slug (chaîne), contenu (texte markdown)
- extrait (texte, max 200 caractères), key_takeaways (tableau json, 3-5 éléments)
- méta_titre (chaîne, ≤60), méta_description (chaîne, ≤160)
- feature_image_alt (chaîne)
- cta_headline (chaîne), cta_button_text (chaîne), cta_button_url (chaîne)
- schema_markup (json - Article schema.org)
- Champs : identifiant
6. Blog_Posts_Tags - jonction pour les articles plusieurs-à-plusieurs ↔ tag
- blog_posts_id (FK → Blog_Posts), blog_tags_id (FK → Blog_Tags)
- Champs : identifiant
Important :
- Utilisez create_collection() pour les 6 collections
- Chaque champ doit inclure le schéma : {} et la méta : {} dans la charge utile
- Définir TOUS les champs à l'avance (aucun champ incrémentiel n'est ajouté)
- Après avoir créé des collections, lancez au minimum 2 exemples d'articles de blog :
* Post 1 : "Introduction à la sécurité de l'IA" - auteur=1, catégorie=33 (IA et LLM), publié
* Post 2 : « Comprendre la conformité SOC 2 » – auteur = 1, catégorie = 37 (Conformité), brouillon
* Pour chaque article, créez uniquement la traduction EN (ignorez AR/DE/ES/FR pour l'instant)
- Vérification de l'impression : répertoriez toutes les collections et leur nombre d'enregistrementsQu'est-ce que cela fait
Lorsque vous exécutez cette invite via un agent avec la compétence Directus chargée :
- Crée 6 collections avec des définitions de champs complètes dans le bon ordre (les parents d'abord, puis les jonctions, puis les traductions)
- Ajoute des relations appropriées - Plusieurs-à-un à partir des publications → Auteurs/Catégorie, Plusieurs-à-Plusieurs via la table de jonction
- Respecte la conception réelle de votre schéma - Les enregistrements de traduction sont une collection enfant distincte liée par
blog_posts_id - Données réelles de départ - Deux exemples de publications avec les champs obligatoires remplis
- Vérifie - Imprime ce qui a été créé
Le code généré (ce qui se passe sous le capot)
L'agent produit quelque chose comme ce script Python :
à partir de directus.client importer DirectusClient
importer le système d'exploitation
client = DirectusClient(
url=os.getenv('DIRECTUS_URL'),
token=os.getenv('DIRECTUS_ADMIN_TOKEN') # jeton d'administrateur pour les opérations de schéma
)
## 1. Blog_Auteurs
client.create_collection('Blog_Authors', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'nom', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'slug', 'type' : 'string', 'schema' : {'is_unique' : True}, 'meta' : {'interface' : 'input'}},
{'field' : 'bio', 'type' : 'text', 'schema' : {}, 'meta' : {'interface' : 'textarea'}},
{'field' : 'avatar', 'type' : 'file', 'schema' : {}, 'meta' : {'interface' : 'file', 'options' : {'accepted_files' : 'image/*'}}},
{'field' : 'social_twitter', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'status', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'select', 'options' : {'choices' : ['draft', 'published']}}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}},
{'field' : 'updated_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## 2. Blog_Catégories
client.create_collection('Blog_Categories', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'nom', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'slug', 'type' : 'string', 'schema' : {'is_unique' : True}, 'meta' : {'interface' : 'input'}},
{'field' : 'description', 'type' : 'texte', 'schema' : {}, 'meta' : {'interface' : 'textarea'}},
{'field' : 'parent_category', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'rated_collection' : 'Blog_Categories'}}},
{'field' : 'icon', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'color', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'color'}},
{'field' : 'sort_order', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'numeric'}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}},
{'field' : 'updated_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## 3. Blog_Tags
client.create_collection('Blog_Tags', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'nom', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'slug', 'type' : 'string', 'schema' : {'is_unique' : True}, 'meta' : {'interface' : 'input'}},
{'field' : 'color', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'color'}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## 4. Blog_Posts - enregistrement de métadonnées parent
client.create_collection('Blog_Posts', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'status', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'select', 'options' : {'choices' : ['draft', 'published']}}},
{'field' : 'published_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : False}},
{'field' : 'auteur', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'rated_collection' : 'Blog_Authors'}}},
{'field' : 'category', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'rated_collection' : 'Blog_Categories'}}},
{'field' : 'featured_image', 'type' : 'file', 'schema' : {}, 'meta' : {'interface' : 'file', 'options' : {'accepted_files' : 'image/*'}}},
{'field' : 'reading_time_minutes', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'numeric'}},
{'field' : 'is_featured', 'type' : 'boolean', 'schema' : {}, 'meta' : {'interface' : 'boolean'}},
{'field' : 'is_pillar', 'type' : 'boolean', 'schema' : {}, 'meta' : {'interface' : 'boolean'}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}},
{'field' : 'updated_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## 5. Blog_Posts_Translations - contenu par langue
client.create_collection('Blog_Posts_Translations', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'blog_posts_id', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'relied_collection' : 'Blog_Posts'}}},
{'field' : 'code_langues', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'select', 'options' : {'choices' : ['en', 'ar', 'de', 'es', 'fr']}}},
{'field' : 'titre', 'type' : 'string', 'schema' : {'is_required' : True}, 'meta' : {'interface' : 'input'}},
{'field' : 'slug', 'type' : 'string', 'schema' : {'is_required' : True, 'is_unique' : True}, 'meta' : {'interface' : 'input'}},
{'field' : 'content', 'type' : 'text', 'schema' : {}, 'meta' : {'interface' : 'markdown'}},
{'field' : 'extrait', 'type' : 'text', 'schema' : {}, 'meta' : {'interface' : 'textarea'}},
{'field' : 'key_takeaways', 'type' : 'json', 'schema' : {}, 'meta' : {'interface' : 'list'}},
{'field' : 'meta_title', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'meta_description', 'type' : 'texte', 'schema' : {}, 'meta' : {'interface' : 'textarea'}},
{'field' : 'featured_image_alt', 'type' : 'text', 'schema' : {}, 'meta' : {'interface' : 'textarea'}},
{'field' : 'cta_headline', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'cta_button_text', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'cta_button_url', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'field' : 'schema_markup', 'type' : 'json', 'schema' : {}, 'meta' : {'interface' : 'json'}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}},
{'field' : 'updated_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## 6. Blog_Posts_Tags - table de jonction
client.create_collection('Blog_Posts_Tags', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'blog_posts_id', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'relied_collection' : 'Blog_Posts'}}},
{'field' : 'blog_tags_id', 'type' : 'entier', 'schema' : {}, 'meta' : {'interface' : 'relation', 'options' : {'rated_collection' : 'Blog_Tags'}}},
{'field' : 'created_at', 'type' : 'timestamp', 'schema' : {}, 'meta' : {'interface' : 'datetime', 'readonly' : True}}
])
## Données d'échantillon de départ
## 1. Créer un auteur (s'il n'existe pas)
author = client.insert({'name' : 'Alex Chen', 'slug' : 'alex-chen', 'bio' : 'Ingénieur en sécurité chez Ainex.', 'statut' : 'publié'}, collection='Blog_Authors')
auteur_id = auteur['id']
## 2. Créer une catégorie
category = client.insert({'name' : 'AI & LLM', 'slug' : 'ai-llms', 'description' : 'Articles sur la sécurité de l'IA et les LLM.', 'sort_order' : 1}, collection='Blog_Categories')
catégorie_id = catégorie['id']
## 3. Créer un enregistrement d'article de blog parent
post = client.insert({
'statut' : 'publié',
'auteur' : auteur_id,
'catégorie' : id_catégorie,
'reading_time_minutes' : 8,
'is_featured' : faux,
'publié_à' : '2026-05-03T10:00:00.000Z'
}, collection='Blog_Posts')
post_id = post['id']
## 4. Créer une traduction en anglais
client.insert({
'blog_posts_id' : post_id,
'code_langues' : 'fr',
'title' : 'Introduction à la sécurité de l'IA',
'slug' : 'introduction à la sécurité ai',
'content': '# Fondements de la sécurité de l'IA\n\nCeci est un exemple d'article sur la sécurisation des systèmes d'IA...\n\n## Risques clés\n\n- Injection rapide\n- Fuite de données\n- Empoisonnement du modèle\n\n## Meilleures pratiques\n\nToujours valider les entrées, l'exécution du modèle sandbox et surveiller les anomalies.',
'extrait': 'Un guide convivial pour les débutants pour comprendre et atténuer les risques du système d'IA.',
'key_takeaways' : ['Les systèmes d'IA sont vulnérables aux attaques par injection rapide', 'Toujours valider et nettoyer les entrées du modèle', 'Surveiller le comportement anormal du modèle'],
'meta_title' : 'Introduction à la sécurité de l'IA - Guide Ainex',
'meta_description': 'Découvrez les principes fondamentaux de la sécurité de l'IA, les menaces courantes et les meilleures pratiques pour protéger vos applications LLM.',
'featured_image_alt': 'Visualisation abstraite de la sécurité des réseaux neuronaux',
'cta_headline': 'Prêt à sécuriser votre IA ?',
'cta_button_text': 'Commencer',
'cta_button_url' : '/get-started',
'schema_markup' : {
'@context' : 'https://schema.org',
'@type' : 'Article',
'headline' : 'Introduction à la sécurité de l'IA',
'auteur' : {'@type' : 'Personne', 'nom' : 'Alex Chen'},
'date de publication' : '2026-05-03T10:00:00.000Z',
'description' : "Un guide convivial pour les débutants pour comprendre et atténuer les risques du système d'IA."
}
}, collection='Blog_Posts_Translations')
print(f"✅ Backend du blog créé ! ID de publication : {post_id}")Ce script est généré et exécuté par l'agent en un seul tour. Vous obtenez immédiatement un schéma complet et interrogeable et des données prédéfinies.
Exemple 2 : Backend de commerce électronique en une seule invite
Allons plus grand. Voici comment créer une couche de données de commerce électronique complète :
Créez un backend de commerce électronique de production avec Directus. Créez ces collections :
1. **Produits** - articles du catalogue
- titre, slug (unique), description (texte), statut (projet/disponible/out_of_stock)
- price_cents (entier), compare_price_cents (entier nullable), cost_cents (entier)
- sku (unique), code-barres (nullable), poids_grammes (entier)
- Inventory_quantity, low_stock_threshold (par défaut 10)
- feature_image (fichier), images (M2M via jonction)
- catégorie (M2O → Catégories), tags (M2M → Tags via jonction)
- publié_à
2. **Catégories** - taxonomie imbriquée
- nom, slug (unique), description, parent_category (nullable M2O self)
- image (fichier), sort_order
3. **Balises** - balises de produits
- nom, slug (unique), couleur (hex)
4. **Clients** - comptes
- email (unique), prénom, nom, téléphone
- default_shipping_address (json), default_billing_address (json)
- total_spent_cents, order_count, last_order_at (nullable)
- statut (actif/bloqué), notes
5. **Commandes** - transactions
- numéro_commande (unique), statut (en attente/en traitement/expédié/livré/annulé)
- client (M2O → Clients)
- subtotal_cents, tax_cents, shipping_cents, total_cents, discount_cents
- adresse_expédition, adresse_facturation (json)
- payment_status (payé/remboursé/échoué), payment_method (stripe/paypal/manuel)
- payment_intent_id (nullable), tracking_number (nullable)
- notes, Cancelled_at (nullable)
6. **Order_Items** - éléments de campagne (table de jonction Commandes ↔ Produits)
- commande (M2O → Commandes), produit (M2O → Produits)
- quantité, unit_price_cents, total_price_cents
- nom_variante (chaîne, nullable)
7. **Inventory_Adjustments** - journal d'audit
- produit (M2O → Produits), motif (réapprovisionnement/vente/retour/audit/ajustement)
- delta_quantity, previous_quantity, new_quantity
- reference_id (nullable - liens vers la commande/ajustement), notes
8. **Collections** - regroupements organisés
- nom, slug (unique), handle (chaîne conviviale pour l'API)
- type (manuel/automatique), règles (json pour les règles automatiques)
- sort_order, publié (booléen)
9. **Réductions** - codes promotionnels
- code (unique, nullable pour automatique), type (pourcentage/fixe)
- valeur (int - pourcentage ou cents), max_uses, used_count
- valid_from, valid_until (nullable)
- apply_to (all/categories/products), applicable_product_ids (tableau json)
- min_order_amount_cents (nullable), Exclure_sale_items (booléen)
Contraintes :
- Utilisez create_collection() pour les 9 collections + tables de jonction explicites (Products_Images si nécessaire)
- Chaque champ : incluez le schéma : {} et la méta : {} à l'avance
- Utilisez les types de champs Directus appropriés : entier, chaîne, texte, booléen, date/heure, json, fichier, pivot
- Ajouter des index uniques sur slug, sku, order_number
- Seed : créez 3 exemples de produits avec des exemples d'images (utilisez les ID de fichier si disponibles), lien vers la catégorie
- Vérifier : appelez get_collections() et imprimez le nombre d'enregistrements
- Afficher le script Python completL'exécution de cette opération via l'agent produit un script d'environ 300 lignes qui crée l'intégralité de votre modèle de données de commerce électronique en une seule fois. De zéro à "voici votre panneau d'administration" avant que votre café ne refroidisse.
Exemple 3 : CRM simple (Contacts + Entreprises + Interactions)
Besoin de quelque chose de léger ? Voici un CRM :
Créez un backend CRM avec Directus :
1. Entreprises
- nom, domaine (unique), secteur d'activité, taille (1-10/11-50/51-200/200+)
- site Web, téléphone, adresse (json), annual_revenue_cents (nullable)
- propriétaire (M2O → Utilisateurs), statut (lead/client/prospect/baratté)
- last_contacted_at, notes (texte)
2. Contacts
- prénom, nom, email (unique), téléphone, titre_emploi
- entreprise (M2O → Entreprises), propriétaire (M2O → Utilisateurs)
- statut (actif/inactif/lead), favorite_contact (email/téléphone/linkedin)
- linkedin_url, notes
3. Interactions
- contact (M2O → Contacts), tapez (appel/e-mail/réunion/note)
- direction (entrant/sortant), durée_minutes (entier)
- résumé (texte), next_steps (texte), due_date (nullable)
- Related_deal (M2O → Offres nullables)
4. Offres
- titre, value_cents, étape (proposition/négociation/closed_won/closed_lost)
- probabilité (0-100), close_date (attendu)
- contact (M2O → Contacts), propriétaire (M2O → Utilisateurs)
- remarques
Contraintes :
- Utilisez create_collection() pour les 4
- Tous les champs standards : id, Create_at, Update_at automatiquement ajoutés
- Relations configurées correctement (les FK sont des entiers)
- Seed : 2 entreprises, 4 contacts, 3 interactions, 1 dealEncore une fois - une invite, une exécution.
Comment ça marche réellement : la sauce secrète de la compétence
Démystifions ce qui se passe lorsque vous demandez à l'agent de « créer un backend » :
Étape 1 : L'agent analyse votre invite
L'agent utilise son raisonnement pour extraire :
- Noms des collections et leur objectif
- Noms de champs, types et contraintes
- Directions des relations (plusieurs-à-un, plusieurs-à-plusieurs)
- Exigences en matière de données sur les semences
- Étapes de vérification
Il n'a pas besoin de voir un langage de définition de schéma - il lit simplement votre anglais simple et construit la structure.
Étape 2 : La compétence génère des charges utiles correctes
C'est là que la compétence brille. Le point de terminaison « POST /collections » de Directus est réputé pour être pointilleux :
Sans schema : {} et meta : {} par champ ? → 403 Interdit (même avec un jeton d'administrateur valide).
Nom de type de champ incorrect ? → La collection n'apparaît pas silencieusement.
Méta-options de relation manquantes ? → Les relations ne fonctionnent pas.
La compétence connaît le contrat de l'API Directus et génère automatiquement des charges utiles correctes pour la production. Voici à quoi ressemble une définition de champ correcte :
{
'field' : 'price_cents',
'type' : 'entier',
'schema' : {'is_required' : True}, # ← toujours inclure ceci
'meta' : {'interface' : 'numeric'} # ← toujours inclure ceci
}Étape 3 : Appels d'API d'administration dans l'ordre
Les collections sont créées par ordre de dépendance (parents avant enfants, pas de dépôts circulaires). Pour chacun :
POST /collectionscrée la collection avec une liste complète de champs- La compétence vérifie avec
GET /collections/{name}qu'elle existe - S'il existe déjà, il est supprimé et recrée (mode idempotent)
Pour les tables de jonction (M2M), le type pivot ou des champs FK explicites avec meta.interface : 'relation' sont utilisés.
Étape 4 - Données de départ + vérification
Une fois que toutes les collections existent, l'agent :
- Insère des exemples d'enregistrements en utilisant le CRUD standard (
insert()) - Requêtes avec
get()ouget_expanded()pour confirmer - Imprime un résumé sur votre terminal
Modèles éprouvés en production que nous avons découverts
Nous avons exécuté ce flux de travail sur « https://hub.aratech.ae » avec notre schéma de blog complet. Voici les leçons :
1. Le schéma et la méta sont obligatoires
On ne saurait trop insister sur ce point. Directus 10+ renvoie 403 lorsque l'un ou l'autre est manquant, mais le message d'erreur est trompeur (« Vous n'avez pas la permission ») lorsque le véritable problème est la structure de votre charge utile.
Toujours vérifier :
- La charge utile de la collection a
schema : {}etmeta : {}au niveau supérieur - Chaque champ a à la fois un « schéma » et une « méta »
- Les valeurs des champs
typesont des types Directus valides (integer, pasint;string, pasvarchar)
2. Utilisez get_expanded() pour les relations
Directus GraphQL nécessite des jeux de sélection imbriqués pour les champs de relation. Si vous essayez :
{ Blog_Posts_Translations { identifiant blog_posts_id } }Vous obtenez une erreur 400 : Le champ "blog_posts_id" de type "Blog_Posts" doit avoir une sélection de sous-champs.
La méthode get_expanded() de la compétence avec notation par points gère cela automatiquement :
client.get_expanded(
field=['id', 'title', 'blog_posts_id.id', 'blog_posts_id.status'],
collection='Blog_Posts_Translations',
limite=50
)
## Renvoie les dicts : blog_posts_id = {'id' : '5', 'status' : 'published'}3. Les identifiants sont des chaînes dans GraphQL, des entiers dans REST
GraphQL renvoie tous les champs id sous forme de chaînes. Mais les champs de clé étrangère (comme blog_posts_id) sont des entiers lorsqu'ils sont utilisés comme filtres ou insérer des valeurs.
Lorsque vous créez une publication et récupérez post_id = "42", vous devez caster :
client.insert({
'blog_posts_id' : int(post_id), # PAS post_id
'code_langues' : 'fr',
...
}, collection='Blog_Posts_Translations')Sinon, vous obtenez une erreur de validation 400 GraphQL.
4. Les écritures de la table de jonction passent par la collection de jonctions
Directus expose les relations M2M sous forme de champs virtuels en lecture seule sur le parent. Vous ne pouvez pas CORRIGER directement le champ tags sur Blog_Posts - vous obtenez 403 Forbidden.
Au lieu de cela, écrivez dans la table de jonction :
## FAUX : Le PATCH direct sur Blog_Posts échoue
client.update(post_id, {'tags': [1, 2, 3]}, collection='Blog_Posts') # ❌ 403
## CORRECT : Insérer dans la table de jonction
pour tag_id dans [1, 2, 3] :
client.insert({'blog_posts_id' : post_id, 'blog_tags_id' : tag_id}, collection='Blog_Posts_Tags')5. Les collections de traduction nécessitent des insertions en deux étapes
Selon notre conception de schéma : créez d'abord l'enregistrement parent Blog_Posts, puis créez l'enregistrement enfant Blog_Posts_Translations avec le FK :
## Étape 1 : parent (métadonnées uniquement)
post = client.insert({
'statut' : 'publié',
'auteur' : auteur_id,
'catégorie' : id_catégorie,
'reading_time_minutes' : 8
}, collection='Blog_Posts')
post_id = post['id']
## Étape 2 : traduction (contenu réel)
client.insert({
'blog_posts_id' : post_id,
'code_langues' : 'fr',
'title' : 'Mon message',
'slug' : 'mon-post',
'content': '# Bonjour tout le monde...',
...
}, collection='Blog_Posts_Translations')6. Le bug de troncature POST et le correctif PATCH
Nous avons découvert que les POST de grands champs de « contenu » (>~ 1 500 caractères) étaient tronqués silencieusement par une couche passerelle/proxy. La réponse REST renvoie le succès, mais seul le premier ~ 1,5 Ko de votre démarque est stocké.
Atténuation : créez l'enregistrement de traduction avec les données initiales, puis corrigez immédiatement le champ « contenu » :
## Étape A : Créer (risque de troncature)
trans = client.insert({ ... initial_fields ..., 'content': 'short placeholder' }, collection='Blog_Posts_Translations')
trans_id = trans['id']
## Étape B : CORRIGER le contenu complet (contourne la troncature)
client.update(trans_id, {'content': full_markdown_body}, collection='Blog_Posts_Translations')7. Vérifiez l'accès en écriture avant de publier
Avant de tenter une écriture critique (publication, mise à jour groupée), effectuez un test léger :
## Envoyer un enregistrement de test minimal
test = {'statut' : 'projet', 'auteur' : 1, 'catégorie' : 33}
test_record = client.insert(test, collection='Blog_Posts')
test_id = test_record['id']
## En cas de succès, supprimez-le
client.delete(test_id, hard=True, collection='Blog_Posts')
print('✅ Accès en écriture confirmé')Si cela échoue avec 403/404, votre jeton ne dispose pas d'autorisations d'écriture - inutile de tenter l'opération complète.
Gestion des erreurs : qu'est-ce qui ne va pas et comment y remédier
Voici le tableau des erreurs de production :
La compétence gère déjà la plupart de ces éléments en interne (v1.4.1+), mais il est bon de connaître les modèles.
Modèles avancés : filtres, relations et champs d'énumération
Filtrage avec les opérateurs
La compétence prend en charge tous les opérateurs de filtres Directus :
## Égal
résultats = client.get(
champs=['id', 'titre', 'statut'],
filters={'statut' : {'_eq' : 'publié'}},
collection='Blog_Posts',
limite=10
)
## Pas égal
drafts = client.get(filters={'status' : {'_neq' : 'published'}}, collection='Blog_Posts')
## Contient (insensible à la casse)
recherche = client.get(
filters={'title' : {'_icontains' : 'sécurité'}},
collection='Blog_Posts'
)
## Dans la liste
résultats = client.get(
filters={'category': {'_in': [33, 37, 42]}},
collection='Blog_Posts'
)
## Supérieur à
cher = client.get(
filters={'price_cents' : {'_gt' : 10000}},
collection='Produits'
)
## Vérification nulle
non contacté = client.get(
filters={'last_contacted_at' : {'_null' : True}},
collection='Contacts'
)
## Combiné (ET ensemble)
résultats = client.get(
filtres={
'statut' : {'_eq' : 'publié'},
'catégorie' : {'_in' : [33, 37]},
'published_at' : {'_not_null' : Vrai}
},
collection='Blog_Posts'
)Expansion des relations à plusieurs niveaux
Avec la notation par points, vous pouvez récupérer des relations imbriquées en un seul appel :
## Récupérer les publications avec l'auteur, la catégorie et les balises
enregistrements = client.get_expanded(
champs=[
'identifiant', 'titre', 'statut',
'auteur.id', 'auteur.nom', 'auteur.email', # relation M2O
'category.id', 'category.name', # relation M2O
'tags.id', 'tags.name', 'tags.slug' # M2M via jonction
],
collection='Blog_Posts',
limite=20
)
## Résultat : chaque enregistrement a des dicts imbriqués comme {'author' : {'id' : 1, 'name' : 'Alex'}, 'tags' : [{'id' : 5, 'name' : 'AI'}, ...]}Champs JSON pour les données flexibles
Utilisez le type json pour les données structurées arbitraires :
client.insert({
'adresse_d'expédition' : {
'rue' : '123 Tech Blvd',
'ville' : 'Dubaï',
'pays' : 'EAU',
'code_postal' : '00000'
},
'métadonnées' : {
'utm_source' : 'newsletter',
'campaign_id' : 'Q2-2026-ai-launch'
}
}, collection='Commandes')Le JSON est stocké de manière native et interrogeable avec des filtres sur des clés imbriquées.
L'API Admin : lorsque vous devez toucher au schéma par programmation
La compétence expose create_collection() et create_field() via l'API REST admin REST de Directus (/collections et /fields/{collection}), et non GraphQL.
Jeton d'administrateur requis. Votre DIRECTUS_API_TOKEN est pour CRUD ; DIRECTUS_ADMIN_TOKEN est destiné aux opérations de schéma.
client = DirectusClient(
url=os.getenv('DIRECTUS_URL'),
token=os.getenv('DIRECTUS_API_TOKEN'), # pour CRUD
admin_token=os.getenv('DIRECTUS_ADMIN_TOKEN') # pour le schéma
)
## Créer une nouvelle collection
client.create_collection('TEST_Items', champs=[
{'field' : 'id', 'type' : 'entier', 'schema' : {'is_primary_key' : True, 'has_auto_increment' : True}, 'meta' : {'hidden' : True, 'interface' : 'numeric', 'readonly' : True}},
{'field' : 'nom', 'type' : 'string', 'schema' : {}, 'meta' : {'interface' : 'input'}},
{'champ' : 'valeur', 'type' : 'entier', 'schéma' : {}, 'méta' : {'interface' : 'numérique'}}
])
## Ajouter un champ à la collection existante (à utiliser avec prudence - peut corrompre le schéma GraphQL)
client.create_field('TEST_Items', 'description', 'text', meta={'interface' : 'textarea'})
## Liste toutes les collections
collections = client.get_collections()
print(collections) # ['Blog_Posts', 'Blog_Authors', ...]
## Obtenir le schéma de collecte
schéma = client.get_collection_schema('Blog_Posts')
print(schema['fields'].keys()) # dict_keys(['id', 'status', 'author', ...])Avertissement : l'ajout incrémentiel de champs via create_field() peut corrompre le schéma GraphQL pour cette collection, provoquant l'échec de toutes les requêtes GraphQL ultérieures avec 400. Le modèle le plus sûr : définissez chaque champ à l'avance dans create_collection().
Rassembler tout cela : votre première session Vibe-Code
Voici à quoi ressemble un flux de travail complet :
#1. Démarrez votre terminal (avec les tokens Directus en env)
$export DIRECTUS_URL="https://hub.aratech.ae"
$ export DIRECTUS_API_TOKEN="sZk-st..." # jeton utilisateur
$ export DIRECTUS_ADMIN_TOKEN="ad-..." # jeton d'administrateur
## 2. Lancer l'agent Hermès
$ hermès
## 3. Donnez l'invite (copier-coller d'en haut)
Vous : Construisez-moi un backend de blog avec Directus. Créez ces collections : ...
## 4. L'agent réfléchit, génère un script, exécute
🤖 Agent : Création de Blog_Authors... ✅
Création de Blog_Catégories... ✅
Création de Blog_Tags... ✅
Création de Blog_Posts... ✅
Création de Blog_Posts_Translations... ✅
Création de Blog_Posts_Tags... ✅
Seeding 2 exemples de posts... ✅
Vérification :
- Blog_Posts : 2 enregistrements
- Blog_Posts_Translations : 2 enregistrements (en uniquement)
- Blog_Auteurs : 1 enregistrement
- Blog_Catégories : 1 enregistrement
- Blog_Tags : 0 enregistrements
✅ Backend échafaudé avec succès ! Votre panneau d'administration Directus sur https://hub.aratech.ae/admin affiche désormais toutes les collections renseignées.
## 5. Vous avez terminé. Vérifiez l'interface utilisateur ou la requête d'administration :
$ python3 -c "de directus.client import DirectusClient; c = DirectusClient(); print(c.get(collection='Blog_Posts', limit=3))"C'est tout. D'une instance Directus vierge à un backend de blog complet avec des relations et des exemples de données en moins d'une minute.
Ce que cela permet : un prototypage rapide à grande échelle
Il ne s’agit pas seulement de gagner du temps sur la création de schémas (même si c’est énorme). Il s'agit de réduire la boucle de rétroaction entre l'idée et le prototype fonctionnel :
- Conception de schéma d'une semaine → 5 minutes pour décrire ce que vous voulez
- Fichiers de migration manuelle → Un script généré et exécuté
- Rédaction de spécifications API → Directus génère automatiquement les points de terminaison REST et GraphQL
- Configuration du panneau d'administration → L'interface utilisateur d'administration Directus est prête immédiatement avec vos collections, champs et autorisations
Vous pouvez désormais lancer un backend spécifique au projet pour :
- Outils internes (inventaire, ticketing, suivi des actifs)
- Portails clients (historique des commandes, tickets d'assistance)
- Modèles de données SaaS (abonnements, locataires, journaux d'utilisation)
- MVP pour le travail client (décrivez le domaine, obtenez un backend instantané soutenu par CMS)
Et comme Directus n’est que votre base de données plus une couche API intelligente, vous êtes propriétaire des données. Pas de dépendance envers un fournisseur. Exportez le SQL à tout moment.
Limitations et pièges
Tous les modèles de backend ne conviennent pas à Directus
Directus est une première plateforme CMS. Il excelle dans :
- Modèles centrés sur le contenu (blogs, actualités, docs, catalogues)
- Données relationnelles structurées (utilisateurs, commandes, produits)
- Applications gourmandes en médias (gestion de fichiers intégrée)
Ce n'est pas idéal pour :
- Systèmes transactionnels à haute fréquence (bancaire, enchères en temps réel)
- Architectures complexes de sourcing d'événements ou CQRS
- Systèmes nécessitant des fonctions ou des déclencheurs SQL personnalisés
- Websockets en temps réel (bien que vous puissiez utiliser ses hooks et webhooks)
Vous devez encore penser à la modélisation des données
Le Vibe-coding ne remplace pas la compréhension de votre domaine. Entrée des déchets, sortie des déchets :
- Champs mal nommés → API déroutante
- Index manquants → requêtes lentes à grande échelle
- Structure plate pour une imbrication profonde → lectures inefficaces
Les échafaudages de compétences en fonction de vos spécifications. Les bonnes spécifications proviennent d’une bonne modélisation.
Les autorisations doivent encore être configurées
La compétence crée des collections et des champs en tant qu'administrateur, mais vous devez toujours configurer des autorisations Directus (rôles et politiques) pour exposer les bonnes données aux utilisateurs frontaux. La compétence ne touche pas encore les autorisations.
Le contenu volumineux nécessite des correctifs
Si vous publiez des articles démarques > 1 500 caractères, utilisez toujours le modèle POST-then-PATCH pour éviter la troncature. La compétence ne se corrige pas encore automatiquement - vous devez la gérer dans votre script d'amorçage.
Conclusion : le backend est désormais une conversation
Construire un backend signifiait autrefois écrire des migrations SQL, configurer un ORM, créer des routes de contrôleur et connecter un administrateur. Des jours ou des semaines de travail.
En 2026, avec Directus et la compétence nikola66, c'est :
- Décrivez votre schéma en anglais simple
- Laissez l'agent générer et exécuter le Python
- Votre backend est en ligne
Nous avons utilisé ce modèle pour lancer trois backends de projet ce mois-ci seulement. Ce qui était autrefois un obstacle – « nous devons d’abord construire la couche de données » – n’est plus un problème. Le goulot d'étranglement s'est déplacé en amont : nous passons désormais notre temps sur la logique produit et l'UX, pas sur la plomberie.
L’ère du vibe-coding est arrivée. Votre backend est prêt quand vous l’êtes.
Prochaines étapes
- Installer la compétence :
npx skills add nikola66/directus-skill - Connectez-vous à Directus : définissez
DIRECTUS_URL,DIRECTUS_API_TOKEN,DIRECTUS_ADMIN_TOKEN - Essayez l'exemple de blog ci-dessus (il crée un schéma multilingue entièrement interrogeable)
- Étendez-le : ajoutez des collections pour les commentaires, les likes, les abonnements et les journaux de webhooks
- Raccordez les autorisations dans Directus Admin → Paramètres → Rôles et politiques
Vous avez des questions ? Écrivez-nous à Telegram @Hermdroid ou ouvrez un ticket sur le référentiel de compétences. Bonne construction.
Temps de lecture : ~9 minutes Catégorie : Tutoriel Balises : Directus, développement backend, agents IA, Vibe Coding, CMS, GraphQL, Python, schéma de base de données, API, No-Code