Web

Strapi : étendre facilement ses fonctionnalités grâce aux plugins

by Olivier Malige 13 juin 2024

Dans le cadre de la migration et la refonte complète d’un site web, nous avons proposé de construire cette nouvelle version en nous appuyant sur un CMS Headless : Strapi.

Ce choix a été fait pour différentes raisons :

  • C’est un CMS Headless open-source publiée sous licence MIT qui s’appuie déjà sur une communauté active de développeurs, ce qui contribue à son développement continu.
  • Strapi est un CMS créé en 2015 en France
  • Il est facile d’étendre ses fonctionnalités grâce à des plugins développés par la communauté ou par nos soins.

C’est d’ailleurs ce dernier point que nous allons développer tout au long de cet article, puisque ce changement de CMS a nécessité de reproduire sur Strapi certaines fonctionnalités présentes sur leur outil précédent et que notre client souhaitait conserver. Cela nous a permis de nous plonger au cœur du développement de plugins pour Strapi.

Je m’excuse d’avance pour la longueur de cet article, mon enthousiasme pour vous partager mon expérience y est pour beaucoup. Pour me faire pardonner, voici le sommaire en lien rapide, enjoy 😉

    Découverte de Strapi et ses plugins

    Avant de rentrer dans les détails techniques, découvrons ensemble les fonctionnalités de base de Strapi.

    Dès son installation, ce CMS propose un certain nombre de fonctionnalités :

    • La possibilité de créer des types de contenus (unique ou multiple selon les besoins),
    • Une bibliothèque de médias, permettant d’importer dans le projet des images, vidéos, documents, etc.,
    • Un système de gestion des rôles et permissions,
    • Un service mail,
    • Un outil d’internationalisation des contenus.

    En d’autres termes, la base de ce que l’on pourrait attendre d’un CMS.

    En regardant de plus près, on remarque un fait intéressant : le back-office contient une entrée “Plugins” dans laquelle nous retrouvons (parmi d’autres) les fonctionnalités listées ci-dessus.

    plugins Strapi

    Cela nous permet de comprendre que la base même de Strapi est conçue grâce à des plugins, chaque plugin contenant la logique d’une fonctionnalité bien précise.

    Bonne nouvelle : Strapi, conçu par ses développeurs avec des plugins et Open-source, permet de créer facilement ses propres plugins pour ajouter des fonctionnalités, avec une documentation dédiée 🎉.

     

    Headless Experience Conf' - Strapi et l'expérience développeur

    Générer un nouveau plugin Strapi

    Une fois un projet Strapi installé et configuré, la création d’un nouveau plugin commence par une simple ligne de commande :

    yarn strapi generate plugin

    Cette commande nous demande un nom pour notre plugin ainsi que le choix du langage de développement (JavaScript ou TypeScript).

    generate plugin

    Une fois ces choix appliqués, nous sommes invités à activer notre plugin au sein de notre projet : pour cela, il suffit de rajouter le code suivant dans un fichier dédié (à créer s’il n’existe pas déjà).

    enable plugin

    Lançons une dernière commande à la racine du projet :

    yarn build

    pour rebuild le back-office de notre CMS et le tour est joué.

    Après avoir activé le plugin que nous venons de générer et rebuild, nous pouvons dorénavant le voir actif dans le back-office du projet :

    bo-enabled-plugin Strapi

    Ceci est la partie visible de ce qui se passe à la génération d’un nouveau plugin, mais en coulisses, la commande citée plus haut a permis de générer dans le code du projet toute l’architecture technique du plugin et les fichiers nécessaires à son fonctionnement.

    Dans le dossier “src” du projet, un nouveau dossier “plugins” a été créé (s’il n’existait pas déjà). Et dans ce dernier, un dossier “my-plugin” qui contient toute l’architecture d’un plugin, à savoir :

    • Un dossier admin, contenant tout ce qui sera visible dans le panneau d’administration (composants, navigation, paramètres, etc.).
    • Un dossier server, contenant ce qui concerne le serveur backend (types de contenu, contrôleurs, middlewares, etc.).

    Comme cela est précisé dans la documentation, Strapi lui-même est headless, c’est-à-dire que l’admin est complètement séparée du serveur. Il est donc possible de faire tourner le serveur backend uniquement grâce à la commande :

    yarn develop

    Et dans le cas où nous souhaiterions travailler à la fois sur la partie backend et frontend, et avoir un “hot reloading” sur les 2, cela est possible avec la commande :

    yarn develop –watch-admin

    Nous allons maintenant voir plus en détail le contenu des dossiers “admin” et “server” qui viennent d’être créés.

    Interface du Plugin Strapi

    Le premier que nous allons étudier est le dossier “admin” qui contient tout le front-end du plugin.

    Pour comprendre comment le personnaliser, il faut avoir en tête que l’admin de Strapi est une application React, dans laquelle on peut insérer d’autres applications React, à savoir l’admin de chaque plugin.

    plugin admin Strapi

    Le fichier index.js est le point d’entrée de cette application, il met à disposition 2 fonctions s’exécutant à différents moments du cycle de vie de l’application :

    • La fonction register() va s’exécuter pour charger le plugin avant même le démarrage de l’application. Elle prend l’application Strapi en cours d’exécution comme argument (app).
    • La fonction bootstrap() s’exécute une fois que tous les plugins sont enregistrés.

    Ces fonctions vont permettre entre autres :

    • d’inscrire le plugin pour qu’il soit disponible dans le panneau d’administration,
    • d’ajouter un nouveau lien vers la navigation principale de l’administration,
    • de créer une nouvelle section de paramètres,
    • d’injecter un composant dans une zone d’injection (détaillée plus bas),
    • d’enregistrer les fichiers de traduction du plugin contenus dans le dossier “translations” (Anglais et Français par défaut).

    Création de composants et personnalisation de l’interface

    Les dossiers “components” et “pages” contiennent les composants React permettant de personnaliser le contenu et l’apparence du plugin au sein du panneau d’administration Strapi.

    Dès l’installation du plugin, une entrée est ajoutée dans le menu principal de l’administration, menant à la page d’accueil de ce dernier :

    plugin strapi work

    Contenant un simple texte d’accueil, elle peut être personnalisée selon les besoins de votre plugin ou simplement désactivée depuis le fichier “index.js” (point d’entrée).

    Page de paramètres

    Comme vu plus haut, en plus de la page d’accueil de notre plugin, nous pouvons aussi créer une page de paramétrage qui permettra à l’utilisateur du back-office de personnaliser le fonctionnement d’un plugin en fonction de ses besoins.

    Pour cela rien de plus simple : il suffit de créer un nouveau dossier dans le dossier “pages” de l’admin (pour l’exemple, une simple duplication de la Homepage par défaut que j’ai renommé “Settings”).

    Ensuite, j’enregistre cette page de paramétrage dans le fichier “index.js” (point d’entrée de l’admin) en y ajoutant la fonction createSettingsSection au sein de la fonction “register” :

    function register

    Ces quelques manipulations permettent de faire apparaître dans la section “Settings“ de la navigation principale de l’admin, une nouvelle section “My plugin” contenant une nouvelle page “General settings”.

    Generate setting plugin Strapi

    Il ne reste qu’à ajouter à cette page les fonctionnalités qui permettront à l’utilisateur de paramétrer le fonctionnement de notre plugin.

    En ajoutant une page d’accueil et/ou une page de paramétrage avec lesquelles l’utilisateur du CMS pourra interagir, nous voulons les créer avec un design et une charte graphique s’intégrant parfaitement dans le panel d’administration de Strapi.

    Le Design System Strapi

    Pour construire les pages et composants de notre plugin, les développeurs ont mis à disposition une ressource précieuse : toute une section de documentation du Design System de Strapi.

    Cette documentation va détailler les règles d’utilisation de nombreux éléments comme par exemple les styles de typographie, les couleurs, l’iconographie, etc.

    Une section est aussi dédiée à l’utilisation de composants menant vers un Storybook documentant une grande partie des composants utilisés dans Strapi.

    Par exemple, voici le rendu de la page d’accueil d’un plugin custom qui permet d’enregistrer des vues filtrées pour l’ensemble des collections présentes dans le CMS.
    Cette page permet à l’utilisateur de retrouver la liste de l’ensemble des vues qu’il a enregistrées, ainsi que de les modifier ou les supprimer :

    Vue favorites Strapi

    Vues favorites Strapî

    Pour créer cette page, il m’a suffit d’assembler son contenu à partir des composants disponibles dans le design system mis à disposition, et y brancher les actions dont j’ai besoin pour mon cas spécifique.
    Cela m’a permis de me concentrer sur la partie fonctionnelle de l’application React sans avoir à trop me soucier du style, et le résultat est identique à ce que l’on retrouve dans le reste des pages du CMS. Autant dire que le gain de temps est très important !

    Après plusieurs jours d’utilisation, j’ai constaté que certains rares composants utilisés dans l’admin de Strapi n’existent pas dans le Storybook, il faut dans ce cas aller fouiller dans le core du CMS pour reproduire ce qui y a été fait.

    Pour la quasi-totalité des composants présents dans le design system, malgré de nombreux cas d’usages présentés, il n’y a pas de documentation précise détaillant les props que chacun de ces derniers peuvent recevoir.
    Dans le meilleur des cas, les exemples suffisent à comprendre comment les utiliser mais parfois, il faut là aussi plonger dans le core du CMS pour y trouver l’ensemble des props qu’un composant peut recevoir et déterminer comment ils pourraient répondre à nos besoins.

    Au final, et malgré ces quelques inconvénients, le design system reste facile à utiliser et représente un gain de temps considérable, permettant de se concentrer sur le fond. Et nul doute que l’équipe de développement de Strapi va continuer de le faire vivre.

    Les zones d’injection

    Au-delà de pouvoir créer des pages pour permettre à l’utilisateur de Strapi d’utiliser notre plugin, les développeurs ont aussi pensé à un moyen d’intégrer des fonctionnalités au sein même des pages natives du CMS : les zones d’injection.

    Au nombre de 4, nous pouvons insérer des composants dans les zones suivantes :

    • Vue liste :
      • actions”: entre les filtres et le bouton de paramétrage de la vue.
      • deleteModalAdditionalInfos”: au bas de la modale affichée lors de la suppression d’éléments.
    • Vue édition :
      • informations”: en haut à droite de la vue d’édition.
      • right-links”: entre les boutons « Configurer la vue » et « Modifier ».

    Dans le plugin cité plus haut et qui permet d’enregistrer des vues filtrées, nous avons vu comment accéder à la page des vues déjà enregistrées.
    Mais pour sauvegarder une nouvelle vue, il nous fallait ajouter une action dans la page de liste des vues.
    Nous avons donc ajouté cette possibilité grâce à la zone d’injection “actions”.

    Avant que nous ne la modifions, voilà à quoi ressemble la page liste de contenus dans Strapi :

    Liste de contenus Strapi

    Pour ajouter cette fonctionnalité, il faut cette fois utiliser la fonction “bootstrap” disponible dans le fichier “index.js” à la racine du dossier “admin” (point d’entrée de l’admin) dans laquelle j’ajoute la fonction “injectContentManagerComponent” :

    fonction “injectContentManagerComponent” Strapi

    Une fois ce code ajouté, mon composant est visible dans toutes les pages liste de collections dans l’admin de mon projet sous la forme de 2 boutons supplémentaires, un pour enregistrer une nouvelle vue et un autre pour avoir un accès rapide aux vues enregistrées pour moi seulement :

    Composant Strapi

    Là encore, grâce aux composants mis à disposition dans le design system de Strapi, le widget de mon plugin s’intègre parfaitement dans l’interface de l’admin.

    Utilisation et personnalisation du backend

    Penchons-nous maintenant plus en détail sur la partie back-end d’un plugin, à savoir le dossier “server” à la racine de ce dernier.

    server file Strapi

    Les fonctions de cycle de vie

    Ce dossier va contenir 3 fonctions de cycle de vie contenues dans les 3 fichiers dédiés :

    • Register : cette fonction est appelée pour charger le plugin, avant le bootstrap de l’application, afin d’enregistrer les permissions, la partie serveur des champs personnalisés, ou encore les migrations de bases de données.
    • Bootstrap : la fonction bootstrap est appelée juste après l’enregistrement du plugin.
    • Destroy : la fonction destroy est appelée pour nettoyer le plugin (fermer les connexions, supprimer les écouteurs, etc.) lorsque l’instance Strapi est détruite.

    Chacune de ces fonctions donne accès à l’instance de Strapi pour interagir avec elle.

    Dans l’exemple ci-dessous où nous avons eu besoin de détecter la création ou la modification d’une entrée dans une collection, nous avons utilisé la fonction “bootstrap” pour créer des écouteurs sur ces événements et exécuter l’action de notre choix à ce moment là :

    fonction “bootstrap” Strapi

    Configuration

    En fonction des besoins de notre plugin, il est possible de déclarer des paramètres de configuration dans le fichier “config.index.js” à la racine du dossier “server”.
    Ces paramètres seront accessibles dans l’objet correspondant à notre plugin dans l’instance Strapi.

    L’utilisateur peut surcharger cette configuration au moment d’activer le plugin, et le fichier ci-dessus contient aussi une fonction de validation “validator” permettant de déclencher une erreur si le schéma ne correspond pas à l’attendu.

    configuration plugin Strapi

    Souvenez-vous au début de cet article : une fois notre plugin généré, il nous a fallu l’activer au sein du fichier “config/plugins.js” qui se situe à la racine de notre projet Strapi.
    Dans ce fichier, à l’activation du plugin, il est possible de passer un objet optionnel “config” comme dans l’exemple suivant :

    Config plugin strapi

    Dans l’exemple ci-dessus, l’utilisateur passe la valeur “false” au moment de l’enregistrement du plugin pour surcharger la valeur définie par défaut.

    Routes, Controllers, Services

    Nous retrouvons dans le dossier “server” du plugin 3 sous-dossiers :

    • Routes
    • Controllers
    • Services

    Quand on crée une API, le point de départ va être de créer une route. Les routes vont permettre à un utilisateur d’interagir avec cette API.

    Par défaut, un nouveau plugin Strapi est généré avec une route par défaut :

    Strapi API gestion

    Initialement, cette route est privée et nécessite des autorisations (à paramétrer par le développeur).
    Il suffit de rajouter la ligne “auth: false” dans l’objet de configuration (comme dans l’exemple ci-dessus) pour la rendre publique.
    Une fois fait, si nous nous rendons sur l’url “localhost:1337/my-plugin” nous obtenons le message suivant :

    Welcome to Strapi 🚀

    Le cheminement pour obtenir ce résultat est assez simple :

    1. L’URL exécute une requête sur la route du plugin “/”
    2. La route appelle le controller nommé “myController
    3. Le controller exécute une fonction “getWelcomeMessage()” exposée par le service “myService”. C’est cette fonction qui affiche le message ci-dessus.

    Ces routes, controllers et services peuvent être totalement personnalisés afin d’ajouter à l’API du plugin toutes les fonctionnalités dont nous aurions besoin.

    Selon le cas, il est fort probable que nous ayons besoin de passer des données de notre dossier “server” vers “admin”.
    Etant donné que Strapi est headless, il n’est donc pas possible d’accéder à la base de données depuis le front-end de notre plugin directement.
    Pour cela, nous pouvons créer des routes qui ne seront accessibles que depuis l’admin, par un utilisateur authentifié par le système d’authentification interne de Strapi.

    Strapi path

    Dans l’exemple ci-dessus, la même route présentée plus haut n’est plus publique et ne sera accessible que par un utilisateur enregistré dans le back-office.

    A noter que pour effectuer une requête au backend depuis le frontend, il faut utiliser le hook “useFetchClient” qui se charge d’injecter les tokens d’authentification dans les paramètres de la requête.
    Ce hook n’est pas mentionné dans la documentation Strapi.
    Heureusement, j’ai pu compter sur la documentation contributeurs pour la trouver et comprendre comment l’utiliser.

    Policies

    Comme vu précédemment, les routes sont protégées par défaut par le système d’authentification de Strapi, qui repose sur des tokens API ou sur l’utilisation du plugin Users & Permissions.

    Il est aussi possible de définir des règles supplémentaires d’accès grâce à un système de policies (exemple : autoriser l’accès à une route uniquement à un rôle précis).
    La validation de cette règle s’effectue toujours avant d’appeler le controller.
    La fonction déclarée dans la règle doit retourner un booléen; Si sa valeur est à “true”, la route continue son exécution, sinon elle va retourner une erreur (qui peut être personnalisée par le développeur).

    Toutes ces règles peuvent être définies dans le dossier “policies” présent à la racine du dossier “server” :

    Strapi policies

    Dans le code ci-dessus, on souhaite autoriser l’accès à notre route uniquement au rôle “Super Admin”.

    Pour appliquer cette règle, il suffit de l’ajouter dans l’entrée “policies” dans l’objet “config” de la route :

    Strapi config policies

    Content-types

    Selon les besoins de notre plugin, il peut être nécessaire de stocker et accéder à des informations dans la base de données du projet.
    Pour faire cela, il est possible de créer un “content-type” qui sera ajouté au projet, mais directement lié et injecté par le plugin.

    Il est possible de créer les fichiers nécessaires à l’enregistrement d’un nouveau content-type dans le dossier “content-types” à la racine du dossier “server”.
    Mais pour nous faciliter la tâche, les développeurs de Strapi ont crée un générateur CLI qui va se charger de le faire pour nous :

    yarn strapi generate content-type

    strapi generate content-type

    Nous renseignons les informations de notre plugin et à la question “Where do you want to add this model?”, nous choisissons de l’ajouter à un plugin existant puis nous pouvons choisir le plugin existant.

    Comme nous pouvons le voir dans la capture ci-dessus, cette commande génère :

    • Le schéma du content-type
    • Un Controller, un Service et une Route basiques pour notre content-type

    Et bien sûr, il sera possible de ne pas l’afficher dans l’admin pour que ce content-type ne serve qu’au fonctionnement interne du plugin.

    Interagir avec les données

    Une fois que nous avons créé un content-type, il faut pouvoir interagir avec. Le but étant de pouvoir créer, consulter, modifier ou supprimer des entrées dans ce type de contenu.

    Pour faire cela, on peut utiliser les API “Entity Service” et “Query Engine” qui permettent de communiquer directement avec la base de données du projet.

    Il est intéressant de noter que ces deux API permettent d’exécuter des opérations quasi similaires telles que :

    • Opérations CRUD (Create, Read, Update, Delete)
    • Filtrage des entités avec opérateurs logiques (comme $and, $or, $not, etc..) afin de créer des requêtes précises
    • Tri et pagination des résultats pour optimiser et gérer les grandes quantités de données
    • Peuplement des relations du content type (par défaut les relations ne sont pas peuplées) via des options spécifiques dans les requêtes

    Cependant certaines opérations sont spécifiques à une API, par exemple les opérations par lot ne sont disponibles qu’avec la Query Engine API tandis que la création et la mise à jour des composants ou zones dynamiques ne sont possibles qu’avec l’Entity Service API.

    La différence entre ces deux APIs est très clairement expliquée dans la documentation :

    • L’API Entity Service est l’API recommandée pour interagir avec la base de données de votre application. Entity Service est la couche qui gère les structures de données complexes de Strapi telles que les composants et les zones dynamiques, dont les couches de niveau inférieur ne sont pas conscientes.
    • L’API Query Engine interagit avec la couche de base de données à un niveau inférieur et est utilisée en coulisse pour exécuter des requêtes de base de données. Elle donne un accès illimité à la couche de base de données, mais ne doit être utilisé que si l’API Entity Service ne couvre pas votre cas d’utilisation.

    Quel que soit le cas d’usage, Strapi met à disposition les ressources nécessaires pour pouvoir interagir le plus efficacement possible avec la base de données.

    Voici comment utiliser ces API pour récupérer l’ensemble des entrées enregistrées dans le content-type que nous venons de créer :

    strapi

     

    Dans le code ci-dessus, la fonction “getContentTypeEntries” contient un exemple d’utilisation des deux API.
    Elles sont toutes les 2 accessibles depuis l’objet “strapi” qui se trouve dans les middlewares, policies, controllers, services ainsi que dans les fonctions de cycle de vie register, bootstrap et destroy.

    Développement d’un plugin Strapi : une aventure instructive et enrichissante

    Lors du développement de mon premier plugin, j’ai vite réalisé que tout était mis en œuvre par les créateurs de Strapi pour permettre aux développeurs d’étendre facilement les fonctionnalités du CMS tout en conservant une interface et une expérience utilisateur unique.
    Un grand nombre de plugins devenus aujourd’hui incontournables sont d’ailleurs à l’initiative de nombreux développeurs qui ont souhaité contribuer à l’amélioration de l’outil.

    J’ai aussi pu constater que les développeurs et la communauté autour de Strapi sont très actifs (un certain nombre de MAJ sont sorties pendant le développement des plugins, et en cherchant un peu j’ai pu trouver de nombreuses ressources, articles et documentations).
    Malgré quelques éléments absents de la documentation du CMS et de son design system, cela m’a grandement aidé à atteindre mon objectif.

     

     

     

    Olivier Malige

    Olivier Malige

    Développeur Front-end passionné depuis 6 ans, je transforme des interfaces esthétiques et fonctionnelles en une expérience fluide et accessible.

    Commentaires

    Ajouter un commentaire

    Votre commentaire sera modéré par nos administrateurs

    Vous avez un projet ? Nos équipes répondent à vos questions

    Contactez-nous