Strapi, la promesse d'un CMS headless avec Node.js
J'ai comme objectif de mettre en place un site internet relativement classique avec la possibilité pour les administrateurs de modifier le contenu textuel et les images, d'ajouter des actualités, de mettre en place un formulaire de contact et une partie un peu plus avancée permettant aux administrateurs de proposer au travers d'un espace restreint des documents à certains de leurs membres.
Un projet web, somme toute, extrêmement basique. Le gros du travail va surtout se trouver sur la partie intégration avec des animations CSS et des transitions entre les pages. J'ai eu l'occasion durant cette dernière année d'intervenir pour un client sur le framework Angular. J'y ai pris beaucoup de plaisir à redécouvrir ce framework. J'apprécie beaucoup son architecture et je pense que cela est dû notamment à l'utilisation de TypeScript. J'ai donc fait le choix de partir sur ce framework, également pour ce projet, en l'hébergeant sur Netlify.
Concernant le backend, j'avoue que je me suis posé la question. Jusqu'à aujourd'hui, j'ai fait beaucoup de PHP et j'apprécie beaucoup l'architecture proposée par le framework API Platform. Mais quelques éléments me dérangent. Déjà pour commencer, cela veut dire que sur ma stack, je vais avoir deux langages : un langage TypeScript pour le front avec Angular et un langage PHP pour le backend. Mais ce n'est pas tout. API Platform propose un espace d'administration mais il est développé avec React Admin. Cela veut donc dire que sur ma stack, je vais avoir en plus d'un backend sur Symfony avec API Platform, deux fronts, un front avec React ainsi qu'un front avec Angular. Malgré mon amour pour PHP, j'ai rapidement écarté cette possibilité.
J'ai été amené également cette année à faire un peu de javascript côté back-end avec notamment le framework NestJS. J'ai beaucoup apprécié l'approche, ce qui semble assez évident dans la mesure où j'ai fait beaucoup de Angular pendant un an. Ayant baigné un petit peu dans l'écosystème Node cette année, j'ai entendu parler du CMS Headless Strapi. Et sur le papier, clairement, cela m'a convaincu de choisir cette solution.
Déjà, pourquoi un CMS Headless ? En fait Strapi va nous exposer une API REST ou GraphQL mais à aucun moment je vais y mettre du html et du css. Je vais donc être vraiment focus back-end et proposer à mon client Angular les endpoint qui lui sont nécessaires.
Cette solution est un projet open source avec une communauté solide et compte plus de 62k stars sur GitHub. En quelques minutes, je peux avoir un backend avec un espace admin et exposer différentes ressources à mon front. Bien sûr, il est possible d'aller plus loin en personnalisant l'administration et les ressources que je souhaite exposer. Sur le papier, en tout cas, ça fait rêver.
Installation de Strapi
Avant d'installer Strapi, il est important de savoir qu'il ne va fonctionner qu'avec les versions LTS de Node. A l'heure où j'écris ces lignes, la version active LTS est la 20 et la version en maintenance est la 18. Sur la documentation, la page Quick Start vous permet de découvrir Strapi rapidement et d'avoir une version hébergée sur Strapi Cloud. Je suis sûr que Strapi Cloud est une solution géniale, mais pour mon projet, c'est clairement hors budget. Je vais préférer une installation manuelle sur un serveur.
Pour créer un projet Strapi, nous n'avons qu'une seule commande à lancer :
npx create-strapi-app@latest my-project
On vous demande d'abord de confirmer la version de Strapi que vous souhaitez utiliser. Il suffit de répondre « y ».
Need to install the following packages:
create-strapi-app@4.25.9
Ok to proceed? (y)
On vous demande ensuite le type d'installation que vous souhaitez, soit une version rapide, soit une version personnalisée. La version rapide utilisera une base SQLite. Pour commencer, on va faire au plus simple et partir avec une base de données SQLite en validant "Quickstart"
? Choose your installation type
❯ Quickstart (recommended)
Custom (manual settings)
On vous propose alors de vous inscrire sur StrapiCloud. Le service est gratuit pendant 14 jours, puis ensuite il faudra passer à la caisse. Dans mon cas, je vais passer cette étape.
? Please log in or sign up.
Login/Sign up
❯ Skip
L'installation peut prendre quelques minutes, le temps de résoudre les dépendances Javascript.
Une fois l'installation terminée, votre navigateur va s'ouvrir et se rendre sur le domaine localhost:1337
vous invitant à créer votre utilisateur administrateur.
Une fois mon compte administrateur créé, je suis redirigé vers le dashboard et afin de découvrir le fonctionnement de Strapi, il m'invite à créer mon premier "Content Type".
Je découvre alors dans le menu de gauche qu'un Content Type peut avoir 3 formes : Collection Type, Single Type, ou Component. Pour le moment, j'ai seulement un type de créé. Il se situe dans Collection Types. Il s'agit du type User. Si je me rends sur ce type, je découvre que j'ai la possibilité d'ajouter des champs supplémentaires et que je peux également configurer la vue de ce type.
Alors déjà pourquoi user est défini en collection type ? Sur Strapi, je vais pouvoir manipuler plusieurs users. En gros, je pourrais avoir demain un endpoint /users me retournant une collection de users. C'est pour cette raison que l'on va se retrouver à définir user en collection type. Il est présent de base puisque lorsque l'on a créé notre administrateur, il est stocké dans ce type là. Pour ce qui est de la configuration de la vue, cela nous permet de définir quels champs peuvent être définis par l'administrateur du site.
Je reviens à mon projet. Je vais devoir définir une page permettant de mettre en valeur l'histoire de l'association. Afin de coller au webdesign, le type est clairement défini en amont. Par exemple, je sais qu'il y aura deux photos avec un texte limité en nombre de caractères. Je peux donc créer un single type répondant spécifiquement aux besoins de l'édition de cette page sur Angular. Je choisis alors "Create a single type" et je définis comme display name
biography.
En cliquant ensuite sur suivant, je vais devoir définir les champs constituant ce type biographie. Je vais tâcher de choisir les bons types de données et surtout de leur donner un nom explicite puisque la personne qui va mettre à jour le site par la suite doit pouvoir s'y retrouver facilement. J'ajoute donc un content
de type long texte ainsi que deux photos first_picture
et second_picture
.
Ensuite, je me rends dans le menu Content Manager et je retrouve bien mon type Biographie dans la rubrique Single types. Je renseigne alors quelques données : un lorem ipsum dans content et deux photos dans first picture. et second picture. L'interface est vraiment bien pensée, c'est très agréable de venir éditer mon contenu.
Pour finir, je clique sur Save puis Publish afin de publier ma page.
Maintenant, je souhaite pouvoir consommer cette donnée depuis Angular. Le système est plutôt bien fait puisque par défaut l'information est accessible seulement si je présente un jeton valide. Pour tester cela, vous pouvez vous rendre dans "Settings" puis "API Tokens" et créer un nouveau jeton. Je vais l'appeler biographie
, lui donner une durée de 7 jours et le définir en Read Only. En faisant cela, automatiquement, ma ressource biographie sera bien disponible au travers du endpoint /api/biography
.
En utilisant un outil comme Postman ou HTTPie, je vais pouvoir effectuer une requête sur mon API REST en définissant le token dans les headers sous la clé Authorization: Bearer
suivi du token.
{
"data": {
"id": 1,
"attributes": {
"content": "Hello Word ! ",
"createdAt": "2024-08-31T13:58:50.912Z",
"updatedAt": "2024-08-31T13:59:41.376Z",
"publishedAt": "2024-08-31T13:59:41.375Z"
}
},
"meta": {}
}
Je récupère bien ma ressource, mais sans les images associées pour le moment. Cela pour une raison très simple. Par défaut, Strapi ne va pas rechercher les ressources associées pour des questions de performance. Typiquement, mon first_picture
ne contient pas uniquement le lien de l'image, mais un objet média riche avec beaucoup d'informations dedans. Je vais avoir par exemple des déclinaisons selon le format que je souhaite utiliser. Si c'est du small, médium, big, ainsi que la taille, le texte alternatif, etc.
Si je souhaite quand même avoir toutes les informations associées, je dois rajouter le paramètre populate
.
curl http://localhost:1337/api/biography?populate=*
Je récupère bien les informations sur mes deux images, mais la taille de mon JSON a considérablement augmenté. Je me retrouve maintenant avec un JSON de 156 lignes. Heureusement, je vais pouvoir lui demander de me retourner seulement le champ content
, ainsi que l'URL de ma première image et l'URL de ma deuxième image.
curl http://localhost:1337/api/biography?fields[0]=content&populate[first_picture][fields][0]=url&populate[second_picture][fields][1]=url
{
"data": {
"id": 1,
"attributes": {
"content": "Hello Word !",
"first_picture": {
"data": {
"id": 1,
"attributes": {
"url": "/uploads/strapi_1_06cd824850.png"
}
}
},
"second_picture": {
"data": {
"id": 2,
"attributes": {
"url": "/uploads/strapi_2_6f05b41291.png"
}
}
}
}
},
"meta": {}
}
Cette première démonstration peut sembler un peu basique, mais c'est un cas d'usage assez fréquent en développement d'applications web. J'ai passé très peu de temps pour configurer Strapi et mettre en place ma page biographie. Cette première partie est maintenant terminée. Dans un second temps, je vais voir comment mettre en place un formulaire de contact avec Strapi.