Blog Headless Drupal 7 min
DrupalDigital Transformation

Headless Drupal

SparkFabrik Team7 min read
Headless Drupal

What are the tools and patterns that Drupal provides for developing a headless CMS?

First, a quick refresher: Drupal is a free and open-source software written in PHP, created in the early 2000s as a university project by Belgian student Dries Buytaert. Over time, it has evolved from a CMS into a full-fledged framework; in fact, today it is frequently referred to as Drupal as a CMF - Content Management Framework - rather than a CMS.

YOU MIGHT ALSO BE INTERESTED IN: Why choose Drupal for complex enterprise websites

The major leap in this direction came during the transition from version 7, which still had an architecture based on its own proprietary code, to version 8, released in November 2015, in which many Symfony components were integrated directly into the Drupal core and Twig became the default template engine for the frontend. Versions 8 and 9, those currently supported, use Symfony 4, while version 10, currently in development, is planned to use Symfony 5 or, if compatible with the respective release schedules, directly Symfony 6. Among the components used, we have DependencyInjection, EventDispatcher, HttpFoundation, Routing, Serializer, and Yaml. These are the foundations upon which Drupal in turn builds with its own building blocks.

Among other things, starting from version 8, Drupal also began versioning its code following Semantic Versioning, so much so that version 9, the current one, is essentially the last version of the 8 branch with deprecated code removed. For many sites, the upgrade from 7 to 8 was a true paradigm shift; the upgrade from 8 to 9, on the other hand, is much more straightforward, and the same will be true for the transition from 9 to 10.

The JSON:API ecosystem

Drupal offers several possibilities. Here we will look at one in particular, which is more complete and better supported than the others, and which above all allows us to achieve our goal without adding custom code, but only through the use of appropriately configured core or contrib modules. This approach revolves around the core JSON:API module.

JSON:API is a specification that defines a standard for exposing and managing data in JSON format over HTTP. Its first draft dates back to May 2013, and today it is at version 1.0. It has its own registered MIME media type and is designed to minimize requests between client and server. The specification was defined independently of Drupal, which in this regard simply implements it in its own core module. It is worth noting, however, that one of the leaders of the API-first initiative for Drupal, Gabe Sullice, also became a maintainer of the specification itself.

The Drupal JSON:API module is part of the core (although it is not enabled by default), and allows us to expose our data in JSON:API format on dedicated endpoints. The other modules in the ecosystem that we will examine are contrib modules, and they extend the functionality of the core module to address specific development needs. All of these modules are supported by Acquia, the organization behind Drupal, or by other important and active companies in the field, such as Lullabot or Centarro.

The first contrib module on the list is JSON:API Extras, which allows you to extend and override the default behavior of the JSON:API module. Then there are Decoupled Router and Subrequests, which can be used in combination to resolve resources via path alias and reduce the number of required HTTP requests. And then Consumer Image Styles, useful for managing responsive images. Let’s look in detail at the features offered by the JSON:API Extras module:

  • change the endpoint prefix, for example from /jsonapi to /api;
  • choose to display counters in entity collections; and above all
  • override the behavior of each exposed resource.

In particular, regarding this last point: for each resource, it is possible to change the exposed name, the path, define the available fields, or disable it entirely. For example, if we have no interest in exposing the users present on the site, we can decide to remove them from JSON:API through Extras. All of this can be managed directly from the administration panel, without the need to write dedicated code.

We have seen that Drupal exposes resources when the unique resource id is specified on their endpoints. But what happens if our frontend does not know the id of the entity it needs to retrieve, but only the path alias?

Decoupled router

Decoupled Router allows us to solve exactly this scenario. Decoupled Router is another contrib module that is part of the JSON:API ecosystem and allows us to retrieve resources via path alias rather than uuid.

In practice, it works like this: once the module is enabled, it is possible to send resource requests to a specific path, /router/translate-path?path=, concatenating the path alias of the desired resource. The module then searches for the resource in the site’s content, following any redirects, until it retrieves the resource identifier. At that point, it returns a page that contains not the resource directly, but the JSON:API URL of the resource.

So, for example, by making a GET request to /router/translate-path?path=/books/flowers-algernon in our project, we get back a page containing the URL with the uuid of our resource, which we can use to retrieve the data we need.

Obviously, a problem remains here, namely the double request: a first one to retrieve the URL, and a second one to retrieve the entity. And this is where the Subrequests module comes to our aid. With this, we have completed the picture of the JSON:API ecosystem and can combine the modules to achieve the desired result with a single HTTP request. Subrequests allows you to send multiple requests in a single call and chain them together so that one request can use the data returned by another.

  • The requestId is the identifier with which we label each of our requests.
  • The uri is the path to which we make the request. RequestId and uri are mandatory.
  • The action is the type of request.

In request 2, the waitFor property is also present: this is used to indicate that this subrequest depends on the response of a previous request. We can see that we built the uri of request 2 with the values obtained from the response to request 1, specifically with the uuid of the book. Ultimately, request 1 goes to the Decoupled Router path which returns the id of the book we are interested in, and request 2 goes to the path that returns the book’s data.

Other parts of the ecosystem

In summary, we have seen how in our architecture each of the modules in the ecosystem builds upon the previous one and allows us to define and specialize its functions further. The result is a set of APIs that is easily configurable, manageable, and highly customizable, as well as — being based on JSON:API — easily communicable and portable.

If we then want to further extend the functionality of our project, before writing custom code we have many other modules available:

  • JSON:API Search API allows you to perform requests on the site’s search indexes using a method similar to the one we just saw.
  • Entity share leverages the exposed JSON:APIs to enable content synchronization between two Drupal sites.
  • Commerce API integrates the features required by Drupal Commerce by leveraging JSON:APIs.

This list of modules is not exhaustive, but it is a good starting point for getting a more complete picture of the JSON:API ecosystem on Drupal.

Final considerations

Here we focused on JSON:API, but this is not the only module or ecosystem available to achieve our goal. It is currently the most complete and well-supported, but there are other options as well, in addition to custom code, of course.

In the Drupal core, there is another module, RESTful Web Services, which for some time was the direct competitor of JSON:API to become the de facto standard for this use case. In the end, JSON:API prevailed, mainly due to the simplicity with which it covers most use cases. JSON:API allows you to expose all entities present on the site with virtually no additional effort, while RESTful Web Services requires more complex custom configuration, data filtering options are more limited and harder to configure, resource requests are less structured compared to those of JSON:API, and collections can only be exposed after configuring dedicated views. In short, a good module, but one that falls behind in some respects compared to the simplicity and flexibility of JSON:API. On the other hand, for specific use cases, such as operations with custom logic or those not strictly tied to entities, RESTful Web Services offers a certainly more flexible framework to leverage, at the cost of greater overall complexity.

GraphQL is supported instead through a contrib module. It is therefore not in the core, and of the three, it is the latest arrival on the scene. In general, being based on GraphQL, it has excellent well-documented specifications, but some things are less agile, such as write operations and client-side content filtering, which JSON:API handles natively without issues. Its great advantage is the ability to precisely define the data you want to retrieve, avoiding the overfetching problem typical of REST APIs.

Headless Drupal - SparkFabrik

Domande Frequenti

Headless (or decoupled) Drupal is an approach where Drupal is used solely as a backend for content management, exposing data via APIs (such as JSON:API) to a separate frontend. This allows you to leverage the power of the Drupal CMS for content management while leaving the presentation layer to modern frontend frameworks.
The main module is JSON:API, which is part of the Drupal core. It implements the JSON:API specification to expose and manage data in JSON format over HTTP. The ecosystem also includes contrib modules such as JSON:API Extras, Decoupled Router, and Subrequests, which extend the core module’s functionality.
In addition to JSON:API, Drupal offers the core RESTful Web Services module, which requires more complex configuration, and the contrib GraphQL module. Each has specific advantages: RESTful Web Services is more flexible for operations with custom logic, while GraphQL offers well-documented specifications but is less agile for write operations.
The Decoupled Router module allows you to retrieve resources via path alias instead of uuid. By sending a request to /router/translate-path?path= with the resource’s path alias, the module returns the JSON:API URL of the resource. Combined with the Subrequests module, it is possible to avoid the double HTTP request.

Get in touch

Follow us on social media
Listen to Continuous Delivery