Que fait Motifs Strapi ?
Guide rapide : Utilisez Strapi comme CMS headless open-source avec des API REST/GraphQL générées automatiquement à partir de schémas de types de contenu. Dans la v5, utilisez l'API Document Service (
strapi.documents()) pour l'accès aux données backend à la place du Service d'entité obsolète. Les réponses de l'API REST utilisent un format plat (data.nomDuChamp, pasdata.attributes.nomDuChamp). Les relations et les médias ne sont PAS peuplés par défaut – passez toujourspopulate. Utilisezqspour construire des chaînes de requête complexes. Les types de contenu sont privés par défaut ; configurez les permissions via le plugin Utilisateurs et Permissions ou les jetons API.
<exigences_critiques>
CRITIQUE : Avant d'utiliser ce Skill
Tout le code doit suivre les conventions du projet dans CLAUDE.md (kebab-case, exports nommés, ordre des imports,
import type, constantes nommées)
(Vous DEVEZ utiliser strapi.documents('api::type-contenu.type-contenu') (API Document Service) pour tout accès aux données backend dans Strapi v5 – l'API Service d'entité est supprimée)
(Vous DEVEZ toujours passer populate lorsque vous avez besoin de relations, médias, composants ou zones dynamiques – Strapi ne retourne AUCUNE relation par défaut)
(Vous DEVEZ utiliser la bibliothèque qs pour construire des chaînes de requête REST API complexes avec filtres, populate et tri – la construction manuelle de chaînes échoue avec des paramètres imbriqués)
(Vous DEVEZ nettoyer et valider à la fois l'entrée et la sortie dans les contrôleurs personnalisés en utilisant this.sanitizeQuery(ctx), this.sanitizeOutput() et this.validateQuery(ctx))
(Vous DEVEZ définir les permissions pour chaque point de terminaison de type de contenu via le panneau d'administration ou la configuration – toutes les routes sont privées par défaut)
</exigences_critiques>
Détection automatique : Strapi, strapi, @strapi/strapi, createCoreController, createCoreService, createCoreRouter, Document Service, strapi.documents, type-contenu, schema.json, api::, plugin::, hooks de cycle de vie, Utilisateurs et Permissions, /api/auth/local, populate, qs.stringify
Quand utiliser :
- Construire des applications gérées par du contenu avec Strapi comme CMS headless
- Définir des schémas de types de contenu (
schema.json) avec champs, relations, composants et zones dynamiques - Interroger l'API REST avec filtres, populate, tri et pagination
- Créer des contrôleurs, services, routes, politiques ou middlewares personnalisés
- Utiliser l'API Document Service pour les opérations CRUD backend avec workflows brouillon/publication
- Implémenter l'authentification JWT avec le plugin Utilisateurs et Permissions
- Ajouter des hooks de cycle de vie aux types de contenu pour des effets secondaires
- Générer des types TypeScript pour les schémas de contenu
Motifs clés couverts :
- Définition de schémas de types de contenu (
schema.json) - Interrogation de l'API REST avec
qs(filtres, populate, tri, pagination) - API Document Service (
findMany,findOne,create,update,delete,publish,unpublish) - Contrôleurs, services, routes, politiques et middlewares personnalisés
- Hooks de cycle de vie (
beforeCreate,afterUpdate, etc.) - Authentification JWT (inscription, connexion, requêtes authentifiées)
- Génération de types TypeScript
Quand NE PAS utiliser :
- Plateformes CMS non-Strapi (utilisez le skill dédié à votre CMS)
- Requêtes directes à la base de données contournant la couche API de Strapi (utilisez le Document Service de Strapi)
- Logique transactionnelle complexe nécessitant du SQL brut (Strapi abstrait la base de données)
Ressources détaillées :
- Pour les cadres de décision et les tableaux de référence rapide, voir reference.md
API Core et REST :
- examples/core.md – Schémas de types de contenu, interrogation de l'API REST, API Document Service, gestion des erreurs
Personnalisation Backend :
- examples/backend.md – Contrôleurs, services, routes, politiques, middlewares, hooks de cycle de vie, middleware Document Service
Authentification :
- examples/auth.md – Authentification JWT, inscription, connexion, rôles et permissions
Philosophie
Strapi est un CMS headless open-source construit sur Node.js (Koa) qui génère automatiquement des API RESTful et GraphQL à partir de schémas de types de contenu. Le contenu est défini via des schémas JSON, géré via un panneau d'administration, et consommé via des points de terminaison API générés.
Principes fondamentaux :
- Contenu piloté par le schéma – Les types de contenu sont définis dans des fichiers
schema.jsonqui décrivent les champs, les relations, les composants et les zones dynamiques. Le constructeur de types de contenu du panneau d'administration fournit un éditeur visuel, mais les schémas sont du code qui réside dans votre dépôt. - API générées automatiquement – Chaque type de contenu obtient automatiquement des points de terminaison CRUD REST (
/api/:apiIdPluriel) et un support GraphQL optionnel. Aucune création manuelle de route/contrôleur nécessaire pour les opérations standard. - API Document Service (v5) – L'API backend pour accéder au contenu depuis du code personnalisé, des plugins et des hooks de cycle de vie. Remplace le Service d'entité de la v4. Utilise
documentId(et non l'idde la base de données) comme identifiant principal. - Brouillon et Publication – Les types de contenu peuvent avoir des workflows brouillon/publication. Le Document Service par défaut sur
status: 'draft'; le contenu publié nécessitestatus: 'published'ou des appelspublish()explicites. - Permission d'abord – Tous les points de terminaison des types de contenu sont privés par défaut. L'accès doit être explicitement accordé via le panneau d'administration (plugin Utilisateurs et Permissions) ou les jetons API.
- Personnalisation backend – Les contrôleurs, services, routes, politiques et middlewares peuvent tous être personnalisés. Strapi suit un modèle de type MVC construit sur Koa.
Motifs Principaux
Motif 1 : Schéma de Type de Contenu
Les types de contenu sont définis dans des fichiers schema.json à ./src/api/[nom-api]/content-types/[nom-type-contenu]/schema.json. Utilisez collectionType pour le contenu multi-document (articles, produits) et singleType pour le contenu mono-document (paramètres du site, page d'accueil).
{
"kind": "collectionType",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": { "draftAndPublish": true },
"attributes": {
"title": { "type": "string", "required": true },
"slug": { "type": "uid", "targetField": "title" },
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author",
"inversedBy": "articles"
},
"blocks": {
"type": "dynamiczone",
"components": ["blocks.hero", "blocks.rich-text"]
}
}
}
Champs clés : uid génère automatiquement des slugs à partir de targetField, les types relation définissent la cardinalité avec inversedBy/mappedBy, component intègre des blocs réutilisables, dynamiczone permet des types de composants mixtes. Voir examples/core.md pour des exemples complets de collection et de type unique.
Motif 2 : Interrogation de l'API REST avec qs
L'API REST accepte des paramètres de requête complexes pour le filtrage, le peuplement, le tri et la pagination. Utilisez toujours la bibliothèque qs pour construire des chaînes de requête.
import qs from "qs";
const query = qs.stringify(
{
filters: { publishedAt: { $notNull: true } },
populate: {
author: { fields: ["name"] },
categories: { fields: ["name", "slug"] },
},
sort: ["publishedAt:desc"],
pagination: { page: 1, pageSize: 25 },
},
{ encodeValuesOnly: true },
);
const response = await fetch(`${STRAPI_URL}/api/articles?${query}`);
const { data, meta } = await response.json();
Points clés : encodeValuesOnly empêche les problèmes d'encodage des crochets, les filtres utilisent des opérateurs ($eq, $notNull, $containsi, $or, $and), un populate ciblé avec fields évite le sur-peuplement (n'utilisez jamais populate=* en production), les composants de zone dynamique utilisent la syntaxe on. Voir examples/core.md pour des exemples complets de filtrage, peuplement et pagination.
Motif 3 : API Document Service (Back-End)
L'API Document Service est utilisée dans les contrôleurs personnalisés, les services, les hooks de cycle de vie et les plugins pour accéder au contenu côté serveur. Elle remplace le Service d'entité de la v4 (supprimé dans la v5).
const CONTENT_TYPE_UID = "api::article.article";
// Trouver le contenu publié (par défaut 'draft')
const articles = await strapi.documents(CONTENT_TYPE_UID).findMany({
status: "published",
filters: { categories: { slug: { $eq: "news" } } },
populate: { author: true },
});
// CRUD utilise documentId (pas l'id de la base de données)
const article = await strapi
.documents(CONTENT_TYPE_UID)
.findOne({ documentId });
const created = await strapi
.documents(CONTENT_TYPE_UID)
.create({ data: { title: "Nouveau" } });
await strapi.documents(CONTENT_TYPE_UID).publish({ documentId });
Points clés : strapi.documents(uid) remplace strapi.entityService (supprimé dans la v5), documentId est l'identifiant persistant entre les locales et les versions brouillon/publice, le statut par défaut est 'draft' (passez status: 'published' explicitement), méthodes dédiées publish()/unpublish()/discardDraft(). Voir examples/core.md pour des exemples CRUD et publication/dépublication complets.
Motif 4 : Contrôleurs Personnalisés
Étendez ou remplacez les actions de contrôleur générées automatiquement. Les contrôleurs gèrent la logique requête/réponse et délèguent aux services. Utilisez createCoreController depuis factories pour hériter des helpers de nettoyage.
// src/api/article/controllers/article.ts
export default factories.createCoreController(
"api::article.article",
({ strapi }) => ({
async find(ctx) {
await this.validateQuery(ctx);
const sanitizedQuery = await this.sanitizeQuery(ctx);
const { results, pagination } = await strapi
.service("api::article.article")
.find(sanitizedQuery);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
},
}),
);
Pourquoi c'est bon : validateQuery + sanitizeQuery + sanitizeOutput imposent un accès aux champs basé sur les rôles, transformResponse() enveloppe la sortie dans { data, meta }
Voir examples/backend.md pour des exemples complets de contrôleurs incluant des actions personnalisées (findBySlug, incrementViews, search).
Motif 5 : Routes Personnalisées
Définissez des routes personnalisées pour exposer des actions de contrôleur personnalisées. Les fichiers de routes personnalisées se placent à côté du routeur principal.
// src/api/article/routes/custom-article.ts
export default {
routes: [
{
method: "GET",
path: "/articles/slug/:slug",
handler: "api::article.article.findBySlug",
config: { auth: false },
},
],
};
Pourquoi c'est bon : Fichier séparé du routeur principal, handler utilise l'UID complet, auth: false pour un accès public, politiques/middlewares attachables par route
Voir examples/backend.md pour la restriction de route avec only, l'attachement de middleware et la configuration de politique.
Motif 6 : Hooks de Cycle de Vie
Enregistrez des effets secondaires sur les opérations des types de contenu. Les hooks sont définis dans des fichiers lifecycles.ts aux côtés du schéma du type de contenu.
// src/api/article/content-types/article/lifecycles.ts
export default {
async beforeCreate(event) {
/* event.params.data -- modifiez pour transformer l'entrée */
},
async afterCreate(event) {
/* event.result -- le document créé */
},
async beforeUpdate(event) {
/* event.params.data -- modifiez avant sauvegarde */
},
async afterDelete(event) {
/* event.result -- nettoyez les ressources liées */
},
};
Pourquoi c'est bon : Les hooks se déclenchent automatiquement sur les opérations du Document Service, beforeXxx peut modifier event.params.data, afterXxx a event.result
Note : Pour les préoccupations transversales dans la v5 (journalisation d'audit, invalidation de cache), préférez le middleware Document Service (strapi.documents.use((ctx, next) => { ... })) enregistré dans src/index.ts. Les hooks de cycle de vie sont toujours disponibles pour les effets secondaires spécifiques au type de contenu.
Voir examples/backend.md pour des exemples complets de cycle de vie incluant la génération de slug, l'abonnement programmatique et la journalisation d'audit.
Motif 7 : Services
Les services contiennent une logique métier réutilisable appelée par les contrôleurs. Utilisez createCoreService pour hériter du CRUD par défaut et ajouter des méthodes personnalisées.
export default factories.createCoreService(
"api::article.article",
({ strapi }) => ({
async findPublished(filters = {}) {
return strapi
.documents("api::article.article")
.findMany({ status: "published", filters });
},
}),
);
// Appelé via : strapi.service("api::article.article").findPublished()
Pourquoi c'est bon : Hérite du CRUD par défaut, les méthodes personnalisées encapsulent une logique de requête réutilisable, appelées depuis les contrôleurs via strapi.service(uid)
Voir examples/backend.md pour des exemples de services complexes incluant des requêtes d'articles liés et des motifs de publication-avec-notification.
Motif 8 : Politiques
Les politiques sont des fonctions en lecture seule qui autorisent ou refusent l'accès à une route. Elles retournent true (autoriser) ou false (refuser) et ne peuvent pas modifier la requête.
// src/api/article/policies/is-owner.ts
export default async (policyContext, config, { strapi }) => {
const article = await strapi.documents("api::article.article").findOne({
documentId: policyContext.params.documentId,
populate: { author: true },
});
return article?.author?.documentId === policyContext.state.user?.documentId;
};
Pourquoi c'est bon : Vérification en lecture seule (pas de mutation de la requête), appliquée par action sur la configuration du routeur principal
Voir examples/backend.md pour l'application de politique aux routes, les politiques de limitation de débit et le chaînage de plusieurs politiques.
<cadre_decision>
Cadre de Décision
API REST vs API Document Service
Où votre code s'exécute-t-il ?
+-- Côté client (navigateur, application frontend)
| +-- Utilisez l'API REST (fetch /api/:apiIdPluriel)
+-- Côté serveur (contrôleur personnalisé, service, plugin, hook de cycle de vie)
+-- Utilisez l'API Document Service (strapi.documents(uid))
Stratégie de Peuplement
Avez-vous besoin de données liées ?
+-- NON --> Ne passez pas populate (réponse plus légère)
+-- OUI --> Quel niveau de contrôle ?
+-- Toutes les relations, 1 niveau --> populate: '*' (pratique mais sur-peuple)
+-- Relations spécifiques --> populate: { relation: { fields: [...] } }
+-- Relations imbriquées
</cadre_decision>