Upgrade Your Drupal Skills
We trained 1,000+ Drupal Developers over the last decade.
See Advanced Courses NAH, I know EnoughExtending GraphQL: Part 1 - Fields
The last blog post might have left you wondering: "Plugins? It already does everything!". Or you are like one of the busy contributors and already identified a missing feature and can't wait to take the matter into your own hands (good choice).
In this and the following posts we will walk you through the extension capabilities of the GraphQL Core module and use some simple examples to show you how to solve common use cases.
I will assume that you are already familiar with developing Drupal modules and have some basic knowledge of the Plugin API and Plugin Annotations.
The first thing you will want to do is disabling GraphQL schema and result caches. Add these parameters to your development.services.yml
:
parameters:
graphql.config:
result_cache: false
schema_cache: false
This will make sure you don't have to clear caches with every change.
As a starting point, we create an empty module called graphql_example
. In the GitHub repository for this tutorial, you will find the end result as well as commits for every major step.
Diff: The module boilerplate
A simple page title field
Can't be too hard, right? We just want to be able to ask the GraphQL API what our page title is.
To do that we create a new class PageTitle
in the appropriate plugin namespace Drupal\graphql_example\Plugin\GraphQL\Fields
.
Let's talk this through. We've created a new derivation of FieldPluginBase
, the abstract base class provided by the graphql_core
module.
It already does the heavy lifting for integrating our field into the schema. It does this based on the meta information we put into the annotation:
id
: A unique id for this plugin.type
: The return type GraphQL will expect.name
: The name we will use to invoke the field.nullable
: Defines if the field can return null values or not.multi
: Defines if the field will return a list of values.
Now, all we need to do is implement resolveValues
to actually return a field value. Note that this method expects you to use the yield
keyword instead of return
and therefore return a generator.
Fields also can return multiple values, but the framework already handles this within GraphQL type definitions. So all we do is yield
as many values as we want. For single value fields, the first one will be chosen.
So we run the first GraphQL query against our custom field.
query {
pageTitle
}
And the result is disappointing.
{
"data": {
"pageTitle": null
}
}
Diff: The naive approach
The page title is always null
because we extract the page title of the current page, which is the GraphQL API callback and has no title. We then need a way to tell it which page we are talking about.
Adding a path argument
Lucky us, GraphQL fields also can accept arguments. We can use them to pass the path of a page and get the title for real. To do that, we add a new annotation property called arguments
. This is a map of argument names to the argument type. In our case, we added one argument with name path that expects a String value.
Any arguments will be passed into our resolveValues
method with the $args
parameter. So we can use the value there to ask the Drupal route matcher to resolve the route and create the proper title for this path.
Let's try again.
query {
pageTitle(path: "/admin")
}
Way better:
{
"data": {
"pageTitle": "Administration"
}
}
Congratulations, MVP satisfied - you can go home now!
Diff: Using arguments
If there wasn't this itch every developer has when the engineering senses start to tingle. Last time we stumbled on this ominous route
field that also takes a path argument. And this ...
query {
pageTitle(path: "/node/1")
route(path: "/node/1") {
...
}
}
... smells like a low hanging fruit. There has to be a way to make the two of them work together.
Attaching fields to types
Every GraphQL field can be attached to one or more types by adding the types
property to its annotation. In fact, if the property is omitted, it will default to the Root
type which is the root query type and the reason our field appeared there in the first place.
We learned that the route
field returns a value of type Url
. So we remove the argument definition and add a types property instead.
This means the $args
parameter won't receive the path value anymore. Instead, the $value
parameter will be populated with the result of the route
field. And this is a Drupal Url
object that we already can be sure is routed since route
won't return it otherwise. With this in mind, we can make the solution even simpler.
Now we have to adapt our query since our field is nested within another.
query {
route(path: "/admin") {
pageTitle
}
}
Which also will return a nested result.
{
"data": {
"route": {
"pageTitle": "Administration"
}
}
}
The price of a more complex nested result might seem high for not having to pass the same argument twice. But there's more to what we just did. By attaching the pageTitle
field to the Url
type, we added it wherever the type appears. Apart from the route
field this also includes link fields, menu items or breadcrumbs. And potentially every future field that will return objects of type Url
.
We just turned our simple example into the Swiss Army Knife (pun intended) of page title querying.
Diff: Contextual fields
I know what you are thinking. Even an achievement of this epic scale is worthless without test coverage. And you are right. Let's add some.
Adding tests
Fortunately the GraphQL module already comes with an easy to use test base class that helps us to safeguard our achievement in no time.
First, create a tests
directory in the module folder. Inside that, a directory called queries
that contains one file - page_title.gql
- with our test query. A lot of editors already support GraphQL files with syntax highlighting and autocompletion, that's why we moved the query payload to another file.
The test itself just has to extend GraphQLFileTestBase
, add the graphql_example
module to the list of modules to enable and execute the query file.
Diff: Adding a test
Wrap-Up
We just created a simple field, passed arguments to it, learned how to attach it to an already existing type and finally verified our work by adding a test case. Not bad for one day's work. Next time we will have a look at Types and Interfaces, and how to use them to create fields with complex results.
About Drupal Sun
Drupal Sun is an Evolving Web project. It allows you to:
- Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
- Facet based on tags, author, or feed
- Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
- View the entire article text inline, or in the context of the site where it was created
See the blog post at Evolving Web