Colorfield logo

Drupalicious

Published on

The state of GraphQL with Drupal 10 (part 1)

Authors
GraphQL
Photo by Fabrizio Conti on Unsplash

In this fist part, we will compare the key differences between the Drupal GraphQL module versions 3 and 4, where to start and various ways to write a schema with version 4. Next articles will focus on specific topics like translation, mutation, persisted queries and decoupling with Gatsby or Next.

GraphQL 3 and 4

TLDR; to start a new project, I won't hesitate for a second and go for GraphQL 4. There is no upgrade path between these two major versions, so using 3 and planning to go for 4 will require a significant migration work.

If you are looking after a comparison of GraphQL with other options, Dries explains it in this post: Headless CMS: REST vs JSON:API vs GraphQL

Here is a summary of the differences between versions 3 and 4.
For both, we will illustrate with an example of a basic Article node type query.

GraphQL 3

  • Is providing the schema out of the box, exposing basically everything from Drupal that is content related
  • This does not cover Configuration Entities out of the box
  • The schema contains many Drupalisms, so it might be harder to grasp by frontenders / API consumers that are not used to Drupal
  • The schema can be extended via a Plugin system, mostly Fields and Types
  • There is a solution for progressive decoupling with Twig + also a few contributed modules for use cases like preview, formatters, ...
  • It relies on a version of webonyx/graphql-php that is not compatible with PHP 8.1. It's working fine with PHP 8.0 but as this PHP version will not receive security support after 26 November 2023 and Drupal 10 requires PHP 8.1, this issue needs to be tackled to be Drupal 10 ready. There is a first attempt.

Here is an example query to get an Article by ID.

{
  nodeById(id: "1") {
    __typename
    entityLabel
    entityId
    entityBundle
    entityUrl {
      path
      routed
    }
    ... on NodeArticle {
      title
      nid
      sticky
      body {
        value
        format
        processed
      }
      fieldTags {
        targetId
        entity {
          entityId
          tid
          entityBundle
          weight
          ... on TaxonomyTermTags {
            description {
              value
              format
              processed
            }
          }
        }
      }
    }
  }
}

To get multiple nodes, this can be done with a nodeQuery that will list all node types by default. Here is how to filter by Article node type.

{
  nodeQuery(filter: { conditions: { field: "type", value: "article", operator: EQUAL } }) {
    count
    entities {
      entityId
    }
  }
}

As we can see, it's pretty complete by default and we can get data from the Entity Type (Node) or its Bundle (Article) but this duplication with different field names can be confusing for the API consumer. Because the naming and structure is Drupal specific this can lead to questions for non Drupal developers:

  • why are entityUrl and body not a scalar (String)
  • what is a bundle
  • are entityLabel and NodeArticle.title producing the same values
  • same question for entityId and NodeArticle.nid
  • why do we have body for Article and description for Tag
  • can we query with articleById, or articlesQuery
  • what is a TaxonomyTerm
  • ...

GraphQL 4

  • Recommended by the project maintainers
  • Developers have to write the schema, the module is mostly a framework to extend and is providing a solid foundation of built-in data producers
  • There is full control over the schema structure
  • The Plugin system is mostly about DataProducer and Schema / Resolvers

Various ways to get a Schema with GraphQL 4

Custom, Compose, Core Schema like v3 or Directives, let's see what we get!

Go custom

If there are only a few parts of Drupal to expose or if the expected query / output is tightly bound to the domain, this is a good way to go, it's very flexible.

To get started, all that we need is a Schema plugin then a declaration of the schema in a .graphqls file. A very minimal implementation is provided in this repository as an example.

Then create a server and head to /admin/config/graphql/servers/manage/minimal/explorer to execute the query.

To get an Article, the query is stripped to the bare minimum, is backend agnostic with clearer field names, no need to know what a node, bundle, nid, entityLabel, ... is.

{
  article(id: 1) {
    id
    title
    author
  }
}

If we use the schema provided by the GraphQL Example module here is how a list of articles query looks. It will use the default limit (10) and offset (0). Neat.

{
  articles {
    total
    items {
      id
    }
  }
}

Now let's take a look at what the community offers. At the time of writing, all solutions below are usable but as they are quite recent, might still contain some bugs. So keep watching their respective issue queue.

GraphQL Compose

This project is sponsored by Octahedroid and inspired by Open Social GraphQL architecture. There is a possible plan to integrate features from v3 contributed modules (metatag, config, ...).

It currently requires this core_version_requirement patch for Drupal 10.

To get an overview of what is covered by the schema, run this query.

{
  composeInfo: graphQLComposeInformation {
    name
    description
    fragments
    entityTypes {
      id
      types {
        id
        type
      }
    }
  }
}

Here is how to run the Article by id and Articles queries.
The multiple Articles query provides nodes and edges with cursor pagination (first, last, before, after, ...).

fragment NodeArticle on NodeArticle {
  id
  title
  langcode {
    id
    name
  }
  image {
    __typename
    url
    width
    height
    styles {
      style
      url
    }
  }
}

{
  nodeArticle(id: 1) {
    ... on NodeArticle {
      ...NodeArticle
    }
  }

  nodeArticles(first: 5) {
    nodes {
      __typename
      ... on NodeArticle {
        ...NodeArticle
      }
    }
    edges {
      __typename
      cursor
      node {
        __typename
        ... on NodeArticle {
          ...NodeArticle
        }
      }
    }
  }
}

GraphQL Core Schema

This project is sponsored by Liip. Its goal is to expose the schema in a way that is close to what v3 proposes, in a more predicatble way and with the possibility to configure what is exposed (a bit like what JSON:API Extras is doing for JSON:API in core).

We can configure schema extensions (Node, Taxonomy, Entity Display, Entity Query, ...), exposed entity types and fields.

GraphQL core schema configuration UI

It provides Paragraphs and Views support and some submodules to get e.g. Metatag covered.

To compare with the v3 query, here is a similar Articles query, with Tags.

query Articles {
  entityQuery(entityType: NODE, filter: { conditions: [{ field: "type", value: "article" }] }) {
    items {
      id
      label
      ... on NodeArticle {
        body
        fieldTags {
          label
        }
      }
    }
  }
}

For more information and comparison with GraphQL Compose, the maintainers are providing excellent documentation on the project page.

GraphQL Directives

This project is sponsored by Amazee Labs. It is a declarative way to build resolvers, so most of the PHP code written in DataProducers, Resolvers or Schema can be delegated to Directives. This is following a similar philosophy that drives Drupal Commerce: do not expose on the UI what a developer can do / won't be touched often and could produce complexity or unwanted changes from less technical users.

What I like about it is the fine grained way to delegate or write your own resolver, so it provides automation and flexibility over what and how we want to expose.

So a schema that looks like this won't need any custom resolver.

type Article @entity(type: "node", bundle: "article") {
  title: String! @resolveProperty(path: "title.value")
  path: String! @resolveEntityPath
  tags: [Terms]! @resolveEntityReference(field: "field_tags", single: false)
}

If a field requires a custom resolver that is not reusable, the directive just needs to be omitted and the custom resolver implemented as usual.

If we need to create a new directive, it can be declared via a Plugin system.

Similarly, for Metatag, we could declare it like this:

metaTags: [MetaTag] @resolveProperty(path: "metatag")

Then we can provide the type definition of what we want to expose.

type MetaTag {
  tag: String!
  attributes: MetaTagAttributes!
}

type MetaTagAttributes {
  name: String
  content: String
  property: String
  rel: String
  href: String
}

As this article is limited to an overview of what currently exists, a complete example based on Directives will be elaborated in another post.

Documentation

Where to start

The Drupal documentation is mostly about v3, for a documentation that is covering both versions, use this Gitbook documentation to get started.

The example modules shipped with the GraphQL module is also a pretty good starting point.

Also to note: the GraphQL module issue queue is on GitHub.

Where to continue

For an exhaustive introduction to the GraphQL module, make sure to watch Building a GraphQL API - Beyond the basics DrupalCon Portland 2022 from Alexander Varwijk (@kingdutch). It comes with this repository.

Open Social and Thunder distributions are inspiring, checking the implementation of both is really worth it to get a deeper understanding of schema building.

I'm really excited about the variety of options that the community is providing, this is showing of versatile GraphQL in Drupal can be and can answer use cases with the freedom of having expressive ways to build the API.