GraphQL is a rapidly maturing solution available as a web service in Drupal 8 and many production sites leverage GraphQL on Drupal. 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 post, 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, entityChanged
has run through Drupal's date formatter.
Retrievals of users operate much the same way. Consider the following example, a 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": "[email protected]" } } }
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": "[email protected]" } } } }
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": "[email protected]" }, "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 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 } } } } }