Makina Blog

Le blog Makina-corpus

Comment mettre en place un site Drupal "Headless" ?


Les différents modules et techniques pour mettre en place une couche de services web sur une base de site Drupal 8.

C'est LE buzzword de 2017 dans le monde de Drupal : les sites "Headless". L'idée sous-jacente est de se servir du CMS uniquement comme d'un back-office, et de consulter les données depuis des applications (applications mobiles, front-end entièrement en Javascript, ou toute autre possibilité).

Il existe aujourd'hui de nombreuses techniques pour mettre en place un site Drupal "Headless", et nous allons les parcourir.

TL;PL (Trop long; Pas lu) : utilisez JSON API

Un peu d'histoire : les services web en Drupal 7

Il existe pour Drupal 7 de nombreux modules dans la communauté pour mettre en place une couche de services web sur un site Drupal :

Services

Services est le module historique de Drupal pour réaliser des interactions avec des services web : c'est une solution complète sans à priori étandant les capacités de Drupal, permettant de réaliser normalement n'importe quelle intégration de services web autour de Drupal. Souvent un peu lourde pour la plupart des besoins, des équipes ont produit les deux modules suivants. Il en existe une version Drupal 8, mais nous ne la recommandons pas.

En Drupal 7, 2 alternatives à Services ont vu le jour :

RestWS

RESTful Web Services est le premier module ciblé sur une API REST dès sa création. Aucun endpoint spécifique n'est déclaré, les ressources sont simplement disponibles à leur URL (node/1) et tiennent compte des en-têtes spécifiés pour renvoyer une page web ou son équivalent json ou xml.

RESTful

Une deuxième approche a été tentée avec le module RESTful, pour mettre en place des fonctionnalités que les développeurs du module ont jugé manquantes dans les autres solutions : versionning de l'API, une orientation développeur où les endpoints doivent être déclarés, une réécriture de la sortie pour éviter d'exposer une nomenclature "Drupal".

Des services web dans le cœur Drupal 8

Le cœur de Drupal 8 intègre désormais plusieurs modules permettant de mettre en place une API répondant en JSON ou XML, en suivant une solution inspirée du module RestWS :

En fait, Drupal 8 intègre le composant de Serialization issu de Symfony permettant de transformer un tableau ou un objet en flux XML ou JSON (et il est possible d'y ajouter le module CSV Serialization (basé sur la bibliothèque PHP league) pour ajouter le support de ce format) :

Pour simplifier, il vous est possible d'interroger un site Drupal et de récupérer les entités Drupal (contenu, utilisateur, terme de taxonomie) en JSON ou XML :

Exemple avec "/node/1?_format=json" :
{ 
  "nid":[ 
    { 
      "value":1 
    } 
  ], 
  "uuid":[ 
    { 
      "value":"9ebbde0f-ddf5-42c8-a795-8655d137d242" 
    } 
  ], 
  "vid":[ 
    { 
      "value":1 
    } 
  ], 
  "langcode":[ 
    { 
      "value":"en" 
    } 
  ], 
  "type":[ 
    { 
      "target_id":"page", 
      "target_type":"node_type", 
      "target_uuid":"97ef6b60-1274-4c41-b1c0-08ba7d6fbd2c" 
    } 
  ], 
  "status":[ 
    { 
      "value":true 
    } 
  ], 
  "title":[ 
    { 
      "value":"Mentions l\u00e9gales" 
    } 
  ], 
  "uid":[ 
    { 
      "target_id":1, 
      "target_type":"user", 
      "target_uuid":"3d75bd80-73a9-476f-8b5c-8c42556526a9", 
      "url":"\/user\/1" 
    } 
  ], 
  "created":[ 
    { 
      "value":1494403679 
    } 
  ], 
  "changed":[ 
    { 
      "value":1494403699 
    } 
  ], 
  "promote":[ 
    { 
      "value":false 
    } 
  ], 
  "sticky":[ 
    { 
      "value":false 
    } 
  ], 
  "revision_timestamp":[ 
    { 
      "value":1494403699 
    } 
  ], 
  "revision_uid":[ 
    { 
      "target_id":1, 
      "target_type":"user", 
      "target_uuid":"3d75bd80-73a9-476f-8b5c-8c42556526a9", 
      "url":"\/user\/1" 
    } 
  ], 
  "revision_log":[ 

  ], 
  "revision_translation_affected":[ 
    { 
      "value":true 
    } 
  ], 
  "default_langcode":[ 
    { 
      "value":true 
    } 
  ], 
  "path":[ 

  ], 
  "body":[ 
    { 
      "value":"\u003Cp\u003EBlabla\u003C\/p\u003E\r\n", 
      "format":"basic_html", 
      "summary":"" 
    } 
  ] 
}

En plus des entités disponibles dans le cœur, il est possible de renvoyer à peu près n'importe quelle donnée grâce au module Views, lui aussi désormais intégré au cœur Drupal 8. Il suffit de créer un affichage "REST Export", et de choisir les formats renvoyés.

   

Cela vous permet de rapidement effectuer un requêtage de n'importe quelle resource, même si vous êtes un webmestre sans compétence de développement. Vous pouvez alors créer une Views JSON sur n'importe quelle URL, renvoyant exactement les champs souhaités, éventuellement réécrits / traités par Views :

[{"title":"Mentions l\u00e9gales","body":"Blabla"}]

Le REST (ah ah) de la communauté

Comme toujours avec Drupal, le cœur fournit une base sur laquelle de nombreux modules se greffent pour ajouter des fonctionnalités.

REST UI

La mise en place de endpoints (points d'accès aux web services) se fait en utilisant l'API de configuration, et dans un premier temps, réserve donc cette fonctionnalité aux développeurs (il vous suffit de lire la documentation officielle du module RESTful Web Services).

Le module REST UI fournit simplement une interface permettant de déclarer ces endpoints directement dans le back-office de votre site Drupal, vous permettant alors de choisir les méthodes disponibles (GET, POST, PATCH, DELETE), les formats (json, xml) renvoyés, et les méthodes d'authentification (cookie ou http si le module est activé) supportées :

En activant la fonctionnalité, vous créez un endpoint sur les URLs par défaut des ressources (par exemple node/1 comme dans l'exemple que j'ai utilisé dans la première partie).

À noter que vous n'avez besoin du module REST UI que dans la phase de développement, en production, vous pouvez vous contenter de déployer la configuration exportée.

Documentation

Partie importante de toute API qui se respecte : la documentation. Si rien n'est pour l'instant prévu dans le cœur (même si il y a une discussion d'intégrer swagger.io dans le cœur), le module Self documenting REST API vient apporter exactement ça, une page qui documente les endpoints disponibles sur /api/doc :

Il y a d'ailleurs une discussion pour intégrer ce module dans le cœur… ou intégrer Swagger. Vous pouvez d'ailleurs utiliser le module OpenAPI pour l'intégrer vous-même.

Une API JS pour aller plus loin

Pour faciliter la mise en place d'applications, la société Acquia a développé une bibliothèque Javascript, waterwheel.js, permettant de requêter des données Drupal avec une API très simple. On trouve également un module jDrupal qui tente de réaliser la même chose.

La solution actuellement préconisée…

Tous les modules précédents sont basés sur ce que propose le cœur de Drupal 8, adapté depuis le module RESTWS de Drupal 7. Et cela fonctionne. Mais ce n'est pas actuellement la solution préconisée, entre autres parce que ces services web sont encore très "drupaliens" et sont difficiles à consommer pour toute personne qui ne maîtrise pas la terminologie du CMS.

JSON API

En effet, quand Drupal 8 est sorti, la norme JSON API n'était pas finalisée, et elle n'a donc pas été intégrée au cœur de Drupal. Depuis, les choses ont évolué et le module JSON API de la communauté tente de compenser cette absence dans le cœur de Drupal, mais il est déjà prévu d'intégrer ce module dans le cœur.

Ce module "remplace" en quelque sorte la couche de services web du cœur : il ne sert que du composant de Serialization et fournit des endpoints alternatifs à ceux du cœur. Vous n'avez plus besoin d'utiliser le module RESTful Web Services.

Exemple avec /jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242 (on utilise les UUIDs) :
{
  "data": {
    "type": "node--page",
    "id": "9ebbde0f-ddf5-42c8-a795-8655d137d242",
    "attributes": {
      "nid": 1,
      "uuid": "9ebbde0f-ddf5-42c8-a795-8655d137d242",
      "vid": 1,
      "langcode": "en",
      "status": true,
      "title": "Mentions légales",
      "created": 1494403679,
      "changed": 1494403699,
      "promote": false,
      "sticky": false,
      "revision_timestamp": 1494403699,
      "revision_log": null,
      "revision_translation_affected": true,
      "default_langcode": true,
      "path": null,
      "body": {
        "value": "<p>Blabla</p>\r\n",
        "format": "basic_html",
        "summary": ""
      },
      "field_chapo": null
    },
    "relationships": {
      "type": {
        "data": {
          "type": "node_type--node_type",
          "id": "97ef6b60-1274-4c41-b1c0-08ba7d6fbd2c"
        },
        "links": {
          "self": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/relationships/type",
          "related": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/type"
        }
      },
      "uid": {
        "data": {
          "type": "user--user",
          "id": "3d75bd80-73a9-476f-8b5c-8c42556526a9"
        },
        "links": {
          "self": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/relationships/uid",
          "related": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/uid"
        }
      },
      "revision_uid": {
        "data": {
          "type": "user--user",
          "id": "3d75bd80-73a9-476f-8b5c-8c42556526a9"
        },
        "links": {
          "self": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/relationships/revision_uid",
          "related": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242/revision_uid"
        }
      }
    },
    "links": {
      "self": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242"
    }
  },
  "links": {
    "self": "http://localhost/formationd8/jsonapi/node/page/9ebbde0f-ddf5-42c8-a795-8655d137d242"
  }
}

Attention, JSON API se concentre sur les objets du site, pas sur la logique métier. Les endpoints suivants sont donc toujours couverts par les modules du cœur de Drupal (et pour ces besoins métier, il est nécessaire d'activer le module RESTful Web Services) :

  • /session/token
  • /user/register
  • /user/login
  • /user/login_status
  • /user/logout

Enfin, pour paramétrer plus finement votre API, vous pouvez utiliser le module JSON API Extras qui permet de personnaliser le rendu de l'API (désactiver des endpoints, ne pas renvoyer tous les champs, changer le format des champs renvoyés (mettre un format de date au lieu des timestamps), modifier les URLs de base des endpoints, …).

Simple Oauth

Bien sûr, il est possible de restreindre la visibilité de nos contenus ou pages en utilisant le système de permissions de Drupal, il est alors nécessaire de s'authentifier (ne serait-ce que pour modifier son profil utilisateur). Le cœur de Drupal permet une simple authentification HTTP, mais le standard des services web aujourd'hui est Oauth 2.

La communauté remédie à ce manque en proposant le module Simple Oauth, qui permet d'intégrer simplement ce type d'authentification. Là encore, le module sera probablement à terme intégré dans le cœur.

Schemata / Docson

Là encore, secteur clé d'une API propre, la documentation. JSON API n'étant pas proposé par le cœur, la documentation associée n'est pas automatiquement proposée non plus par les modules que nous avons cité précédemment.

C'est toujours du côté de la communauté qu'on trouvera les modules Schemata et Docson qui vous permettront d'inspecter les schémas JSON renvoyés par votre API.

Performance

Plus on augmente la taille ou la complexité d'un site Drupal, plus il est fréquent de créer des liens entre les différents objets manipulés par le CMS (notamment au travers d'EntityReferences). Cela peut occasionner pour l'application qui consomme l'API un grand nombre de requêtes pour récupérer la grappe d'objets complète.

JSON API vous permet déjà de spécifier dans votre requêtes un attribut "include" permettant de récupérer les objets liés.

Mais pouvez également créer plusieurs objets en une seule requête en ajoutant le module (issu de la communauté) subrequests : subrequests permet tout simplement d'envoyer plusieurs requêtes JSON en 1 seul fois, permettant à Drupal ne de démarrer (bootstraper) qu'une seule instance pour répondre aux requêtes.

Il semble que le module JSON API permette également de ne requêter que certains champs, diminuant ainsi la taille du résultat à transférer (gain réseau) et à parser (gain d'exécution) pour vos applications.

Et côté code ?

Tout ça, c'est très bien, mais en tant que développeur, quels sont nos possibilités ?

L'artisanat

Déjà, il est toujours possible de renvoyer du JSON personnalisé dans n'importe quel contrôleur :

return new \Symfony\Component\HttpFoundation\JsonResponse($values);

Serialization

Ensuite, toujours grâce aux composants Symfony de Drupal 8, ici Serializer, la transformation de n'importe quelle entité en JSON (ou inversement la récupération d'une entité à partir de JSON) est rapide à mettre en place :

$output = $this->serializer->serialize($entity, 'json');
$entity = $this->serializer->deserialize($output, \Drupal\node\Entity\Node::class, 'json');

Les transformations sont possibles dans tous les sens :

Endpoint personnalisé

Enfin, pour utiliser le module de services web du cœur de Drupal, vous pouvez toujours déclarer un endpoint personnalisé, c'est une simple classe (un plugin, configurable par annotation donc) :

<?php
/**
 * @RestResource(
 *   id = "my_resource",
 *   label = @Translation("My resource"),
 *   uri_paths = {
 *     "canonical" = "/my-resource/{id}"
 *   }
 * )
 */
class MyResource extends ResourceBase {
  /**
   * Responds to GET requests.
   */
  public function get($id = NULL) {
    if ($id) {
      // Insert here your business code.
      $record = db_select(…)->fetchAssoc();
      if (!empty($record)) {
        return new ResourceResponse($values);
      }
      throw new NotFoundHttpException("Reason");
    }
    throw new HttpException("Reason");
  }
}

Ici, nous ne répondons qu'au GET, et les autres requêtes recevront une réponse 405 ("Méthode non autorisée"). De plus, la ressource sera sérialisée au format demandée lors de la requête, de façon automatique, grâce à l'API.

Comme toujours, la console Drupal vient vous aider dans la génération de ce code :

drupal generate:plugin:rest:resource (OU drupal gprr)

Les CORS

CORS signifie Cross-Origin Resource Sharing et détermine donc les domaines qui sont autorisés à "partager" des ressources. En fait, sous ce terme ce cache une configuration que vous devez réaliser, afin d'indiquer à Drupal quels sont les domaines autorisés à effectuer des requêtes à votre application, notamment depuis des requêtes AJAX.

Dans Drupal, cette configuration se fait dans le fichier /sites/default/settings.yml, par exemple :

cors.config:
enabled: true
  allowedHeaders:
    - '*'
  allowedMethods:
    - '*'
  allowedOrigins:
    # Note: you need to specify the host + port where your app will run.
    - localhost:8000
  exposedHeaders: false
  maxAge: false
  supportsCredentials: false

Pour aller plus vite : Contenta CMS

Et si tout le travail était mâché pour vous ? C'est ce que des développeurs de la communauté essaient de réaliser : Contenta CMS, une distribution API first. Le principe est de mettre en place un site Drupal… sans front-office, uniquement un back-office d'administration exposant une couche de serveur web.

Le travail est commencé, à la fois pour fournir une pré-configuration la plus faible possible de Drupal (voir le profil d'installation API Starter) et une base de modules permettant un déploiement rapide de services web REST, le sus-nommé ContentaCMS (qui contient même une configuration de base pour éviter les problèmes de CORS) :

 

Cette organisation GitHub vous propose d'ailleurs des exemples d'applications (React, Elm) qui consomment les contenus d'exemples proposés par la distribution.

À vous maintenant :

composer create-project contentacms/contenta-jsonapi-project MYPROJECT --stability dev --no-interaction

Pour aller encore plus vite

Mais la société Acquia a publié également sa distribution : Reservoir. Vous disposez de moins de choix, mais êtes encore plus guidés dans la mise en place pour obtenir rapidement une API fonctionnelle :

Essayez-la également :

composer create-project acquia/reservoir-project MY_PROJECT --stability=alpha

Pour aller plus loin

Veille

L'intégration de JSON API dans Drupal est très dynamique en ce moment. Je vous recommande de suivre le développeur à l'origine de tout ça (ContentaCMS, JSON API, Simple OAuth, mais également les modules Drupal 7 Restws ou RESTful), Mateu Aguiló Bosch, afin d'être au courant des dernières évolutions associés aux services web REST au sein de Drupal.

Il existe également un groupe "Headless Drupal" sur le site communautaire de Drupal, mais son actualité n'est pas d'une excellente qualité, et l'on peut tout à fait s'en passer aujourd'hui.

Sinon, vous pouvez tout simplement suivre notre formation à la construction de site en Drupal !

Des alternatives

L'utilisation de la JSON API est la solution que nous préconisons aujourd'hui. Cependant, ce n'est pas la seule possibilité. Selon votre besoin, vous pourrez utiliser les modules du cœur comme vu au début de cet article.

Attention, ils ne gèrent pas tout non plus : il leur manque par exemple la gestion des révisions (que vous pourrez trouver dans le module RELAXed Web Services, une suite de modules pour gérer bien d'autres cas d'utilisation autour de services web).

La synchronisation entre contenus n'est pas non plus géré. Ici, c'est le module Replication, qui s'appuie sur le module précédent, qui entre en jeu.

Récupérer autre chose que des contenus

Un exemple inspiré d'un autre CMS

Le CMS Plone, dans sa version 5, intègre également une API REST. Contrairement à Drupal, elle ne se concentre pas uniquement sur les contenus, mais récupère également les composants de page comme le menu ou le fil d'Ariane.

L'équivalent Drupal

Il n'y a actuellement pas d'équivalent Drupal dans le cœur, mais la communauté tente de permettre le requêtage REST non seulement de l'arborescence du menu (le cœur y travaille également), mais également de l'ensemble des blocs correspondant à une URL.

Le futur

Quelles évolutions attendons-nous des services web REST au sein de Drupal ? Peut-être bien QraphQL, dont les possibilités de requêtage complexe permettront peut-être de dépasser JSON API (qui va déjà assez loin dans ce domaine).

Bien sûr, cette technologie est déjà intégrée dans Drupal.

Et vous, quelle solution utilisez-vous ?

Si vous avez besoin de conseils ou d'assistance sur vos projets Drupal 8 Headless, conatctez-nous !

Formations associées

Formations Drupal

Formation Drupal Administrateur

Toulouse Du 22 au 24 mai 2024

Voir la formation

Formations Drupal

Formation Drupal Développeur

À distance (FOAD) Du 2 au 4 avril 2024

Voir la formation

Actualités en lien

Image
Migration Drupal 7 à Drupal 10
04/04/2024

Migration d'un site Drupal 7 en Drupal 10

Trucs, astuces et "bouts" de code pour migrer votre site web de Drupal 7 à Drupal 10. Compte-rendu d'une conférence donnée au Drupalcamp Rennes 2024.

Voir l'article
Image
Formation Migration Drupal 10
03/04/2024

Du nouveau dans notre gamme de forma­tions Drupal

Maîtri­sez le CMS Drupal de bout en bout avec notre panel complet de forma­tions couvrant la migra­tion (notre petite dernière), l’ad­mi­nis­tra­tion, le déve­lop­pe­ment et l’in­té­gra­tion Drupal. Pour deve­nir expert, plon­gez dans l’uni­vers Drupal !

Voir l'article
Image
Encart article DrupalCamp 2024
06/03/2024

Makina Corpus, parte­naire du Drupal­Camp 2024

Nous sommes fiers d’an­non­cer que Makina Corpus est le spon­sor du Drupal­Camp à Rennes. Notre expert vous y propose une confé­rence « migrer de Drupal 7 à Drupal 10 ».

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus