preston.so

Immersive Content and Usability is the first-ever book on immersive content and spatial content design, available now from A Book Apart.

Writing

GraphQL in Drupal: An exclusive excerpt from the forthcoming book, Decoupled Drupal in Practice

October 18, 2018

Reprinted 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.

Both raw and formatted output available in Drupal's GraphQL implementation

Figure 14-1. The GraphQL module allows us to designate whether we desire raw or formatted output from Drupal. In this case,

entityChanged
has run through Drupal's date formatter.

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"
      }
    }
  }
}

Aliasing field names in GraphQL queries

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."
      }
    }
  }
}

Using fragments to conditionally include fields

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.

Retrieving entity collections with Drupal's GraphQL implementation

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.

Before you go ...

Subscribe to my occasional newsletter. No spam. Just resources and exclusive ideas about omnichannel content and more. Also, be the first to know when my book Immersive Content and Usability is launched.

Trusted by

  • Genero
  • Srijan
  • Tag1 Consulting