GraphQL in Drupal: An exclusive excerpt from the forthcoming book, Decoupled Drupal in Practice
October 18, 2018Reprinted from the Acquia Developer Center with permission from DC Denison (Senior Editor, Acquia).
Over the last few years, I have had the privilege of sharing insights and tutorials on decoupled Drupal, which was originally unknown territory with shifting sands but today is a widely adopted approach, including by some of Acquia's most influential customers. Nonetheless, the relative unavailability of developer-focused resources that are both authoritative and current has hindered architects' and developers' ability to evaluate and explore decoupled Drupal for themselves.
Luckily, next month, my new book Decoupled Drupal in Practice will be officially on the market. With a foreword by Acquia CTO and co-founder and Drupal project lead Dries Buytaert, it is the first and only holistic guide available for developers interested in architecting and implementing decoupled Drupal across the stack. You can now preorder Decoupled Drupal in Practice on Amazon and on Apress, and it is an absolute necessity for any Drupal developer investigating decoupled Drupal.
Wherever on the stack you work, Decoupled Drupal in Practice is a must-have book on your developer shelf. If you build single-page JavaScript applications in React, Angular, Vue.js, or Ember; if you're interested in authentication methods like OAuth 2.0 and JSON Web Tokens and third-party libraries that help you consume Drupal's web services with ease; or if you want to know about the ins and outs of the APIs found in Drupal core and in contributed modules, like core REST, JSON API, and GraphQL, this book has something for you.
Perhaps most relevantly for developers who have already worked with decoupled Drupal in the past, the final chapters in the book deal with advanced topics like the REST resource plugin system, caching in decoupled Drupal architectures, and contributed modules that accelerate the performance of your consumer applications.
To showcase some of the content available in Decoupled Drupal in Practice, find below an excerpted chapter from the book that covers GraphQL in Drupal and the retrieval of both individual entities and entity collections. For more about installing GraphQL as well as more depth about GraphQL entity collections, preorder your copy of the book today.
GraphQL is a rapidly maturing solution available as a web service in Drupal 8. Though it is still under heavy development, many aspects of the module are stable, and many production sites leverage GraphQL on Drupal. As we saw in Chapter 8, GraphQL is particularly robust as a web service due to its focus on tailored responses and readily available introspection layer.
In addition, upon installation, GraphQL provides a built-in debugging tool and user interface named GraphiQL that allows us to issue queries and inspect responses in real time, located at
/graphql/explorer
. In this chapter, we will be using this debugger extensively due to its ease of use. To issue a request to Drupal's GraphQL implementation, all we need to do is produce a GET
request to the URL /graphql
with the query parameter ?query=
, followed by our query, formatted as a URL-encoded string.In this chapter, we retrieve content entities through GraphQL and demonstrate some of the features through the Drupal implementation of GraphQL. The GraphQL module adds a variety of permissions that allow users of various roles to execute arbitrary queries, bypass field security, or access the GraphiQL interface, among others. These are assigned to administrators only by default.
Retrieving entities with GraphQL
Unlike other modules such as RELAXed Web Services and JSON API, the GraphQL module offers a more specific and less generic set of GraphQL fields that map to Drupal equivalents. For instance, whereas RELAXed Web Services and JSON API make no distinction between nodes and users, instead treating them as generic entities, the GraphQL module treats them separately.
Note: GraphiQL offers several convenient keyboard shortcuts to access certain features. To prettify the query you have inserted, use the keyboard shortcut Shift+Ctrl+P. To run the query, use Ctrl+Enter. To access an autocomplete dropdown when providing fields, use Ctrl+Space.
Retrieving individual entities
To retrieve an individual node entity, we can issue the following anonymous query. The
nodeById
field accepts two arguments: id
, the identifier of the node, which should be provided as a string, and language
, the language of the node, which should be provided as a LanguageId (a GraphQL module-provided type that obligates language codes in capital letters without quotation marks, e.g. EN
, FR
). The language
argument defaults to null
and is hence optional.{
nodeById(id: "1", language: EN) {
title
}
}
The query above yields the following response, as we would expect. Note that we are using content generated through Devel Generate (see Chapter 7) in this scenario.
{
"data": {
"nodeById": {
"title": "At Autem Hos Nostrud Saluto Voco"
}
}
}
As you can see in the example above, we can drill down into the node to access the fields contained therein, such as
title
. While these fields map on to their Drupal equivalents, as we have seen in previous responses from core REST and JSON API (e.g. status
, changed
, created
, etc.), the GraphQL module also makes preformatted fields available, such as entityLabel
. Consider the following query.{
nodeById(id: "1") {
entityLabel
changed
entityChanged
}
}
This query yields the following response, as seen in Figure 14-1. As you can see, whereas
changed
yields a UNIX timestamp, similarly to the other APIs we have covered so far, requiring us to perform date handling on the consumer, entityChanged
provides us instead with the date according to Drupal's default date formatter. This is a powerful outcome and means that we can simultaneously take advantage of raw and formatted output from Drupal at the same time.{
"data": {
"nodeById": {
"entityLabel": "At Autem Hos Nostrud Saluto Voco",
"changed": 1536169822,
"entityChanged": "2018-09-05T17:50:22+0000"
}
}
}
Note: From this point forward, most figures in this chapter are GraphiQL screenshots showing identical responses to those presented in the text.
Figure 14-1. The GraphQL module allows us to designate whether we desire raw or formatted output from Drupal. In this case,
has run through Drupal's date formatter.entityChanged
Retrievals of users operate much the same way. Consider the following example, a
userById
query that retrieves a user entity, whose fields adhere to the User
type defined in the GraphQL module.{
userById(id: "2") {
uid
name
mail
}
}
This query yields the following response.
{
"data": {
"userById": {
"uid": 2,
"name": "chifrothaw",
"mail": "chifrothaw@example.com"
}
}
}
We can also retrieve relationships within the entity itself. Consider the following example query, which fetches a node entity along with its author. In this query, we include fields that adhere to the
Node
type for the first level, but because entityOwner
is of type User
, we must use fields from the User
type definition. We are also using aliases (see Chapter 8) to improve the experience for the developer building our consumer.{
entity: nodeById(id: "2") {
title: entityLabel
created: entityCreated
author: entityOwner {
id: uid
name
email: mail
}
}
}
The result looks something like this, as you can see in Figure 14-2.
{
"data": {
"entity": {
"title": "Aliquip Quia",
"created": "2018-09-05T17:50:22+0000",
"author": {
"id": 1,
"name": "admin",
"email": "admin@example.com"
}
}
}
}
Figure 14-2. In this example, we use aliases to improve the consumer developer experience in addition to including information about the user who authored this entity.
As you may have noticed, while we have certain crucial information about the entity, such as when it was created or changed, who created it, and what it is called, we lack other information such as the actual body of the content entity. This is due to the fact that while the Body field is required in nodes of type Article and Page, it is fully possible in Drupal's content modeling system to do without the Body field.
Whenever we create a new content type in Drupal, as you may recall from our study of JSON API, all content entities of that type are assigned a bundle. Within the Drupal implementation of GraphQL, there is a clear distinction between the overarching
Node
type, which governs all nodes irrespective of their bundle, and individual NodeArticle
and NodePage
types, which include bundle-specific information like the Body field.Consider the following example. In this scenario, we are using a fragment to designate that we should only retrieve the body if the node in question is an article.
{
entity: nodeById(id: "2") {
title: entityLabel
created: entityCreated
author: entityOwner {
id: uid
name
email: mail
}
...body
}
}
fragment body on NodeArticle {
body {
value
}
}
Recall that we can also inline this fragment to avoid repeating the field name multiple times.
{
entity: nodeById(id: "2") {
title: entityLabel
created: entityCreated
author: entityOwner {
id: uid
name
email: mail
}
... on NodeArticle {
body {
value
}
}
}
}
The result of this query is the following, as seen in Figure 14-3.
{
"data": {
"entity": {
"title": "Aliquip Quia",
"created": "2018-09-05T17:50:22+0000",
"author": {
"id": 1,
"name": "admin",
"email": "admin@example.com"
},
"body": {
"value": "Abico ideo ratis scisco. Accumsan dignissim ea fere in quadrum venio volutpat. Facilisis genitus ideo immitto jugis magna neque pecus quae. Ad huic in jumentum meus nutus. Blandit nutus pecus ut. Aliquip commoveo inhibeo metuo."
}
}
}
}
Figure 14-3. We can inline a fragment based on the values of the specific bundle that we are targeting. In this case the body will only be included in the response if the entity is an article.
Note: Drupal's implementation of GraphQL makes a variety of queries available that retrieve individual entities and are well beyond the scope of this overview, including
blockContentById
(custom block content), commentById
(comments), contactMessageById
(contact form submissions), fileById
(file entities), shortcutById
(shortcuts), taxonomyTermById
(taxonomy terms), and nodeRevisionById
(node revisions). GraphiQL's autocomplete and documentation features can help you explore what fields are available in those queries.Retrieving entity collections
In addition to queries that retrieve individual entities by identifier, the GraphQL module also offers collection queries that can perform arbitrary operations across a range of entities, such as
nodeQuery
and userQuery
. Consider the following example.{
collection: nodeQuery(limit: 20) {
entities {
title: entityLabel
}
}
}
This query yields a collection of twenty entities, as you can see in Figure 14-4.
Figure 14-4. In this query, we retrieve a collection of entities but limit the response to twenty entities.
In the Drupal implementation of GraphQL,
nodeQuery
takes several arguments: limit
, the number of entities included in the response (defaults to 10); offset
, the number of entities to skip before an entity should figure in the response (defaults to 0); sort
, which dictates how the entities should be sorted; filter
, which provides arbitrary filters; and revisions
, which dictates whether revisions should be included or not. The default value of the revisions
argument is DEFAULT
, which loads current revisions; ALL
loads all revisions and LATEST
loads only the most recent revision (all values expressed without quotation marks as they adhere to their own type definition).To also retrieve the body of these entities, we can use the following query, which drills into the per-bundle implementations. In the following example, we only include the body for articles.
{
collection: nodeQuery(limit: 20) {
entities {
title: entityLabel
... on NodeArticle {
body {
value
}
}
}
}
}
As of now, there is no way in the GraphQL specification to include multiple types on a single fragment. This means that to include the body for page entities as well, we must create another fragment using
...
referring to NodePage
, as you can see in the example query below.{
collection: nodeQuery(limit: 20) {
entities {
title: entityLabel
... on NodeArticle {
body {
value
}
}
... on NodePage {
body {
value
}
}
}
}
}
For more about retrieving entity collections in Drupal's GraphQL implementation, as well as information about installing GraphQL on your Drupal site, preorder your copy of Decoupled Drupal in Practice today.