GraphQL Meets Drupal

Sebastian Siemssen

 @thefubhy

Thanks for your inspiration and support

Moshe Weitzman
Preston So
Wim Leers
Dries Buytaert
Acquia
 

What's inside?!

Motivation

The Limitations of REST

Overfetching

Underfetching

Multiple round trips

Versioning

...

Let's look at a non-hypotethical, real world example

Desired information

Full name of a person

Movie appearances (name of movie)

Home planet (name of planet)

https://swapi.co/api/people/1

Overfetching

Unless specifically designed for a given purpose, you often have to deal with needlessly large and bloated responses.

{
    "name": "Luke Skywalker", 
    "height": "172", 
    "mass": "77", 
    "hair_color": "blond", 
    "skin_color": "fair", 
    "eye_color": "blue", 
    "birth_year": "19BBY", 
    "gender": "male", 
    "homeworld": "http://swapi.co/api/planets/1/", 
    "films": [
        "http://swapi.co/api/films/6/", 
        "http://swapi.co/api/films/3/", 
        "http://swapi.co/api/films/2/", 
        "http://swapi.co/api/films/1/", 
        "http://swapi.co/api/films/7/"
    ], 
    "species": [
        "http://swapi.co/api/species/1/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/14/", 
        "http://swapi.co/api/vehicles/30/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/22/"
    ], 
    "created": "2014-12-09T13:50:51.644000Z", 
    "edited": "2014-12-20T21:17:56.891000Z", 
    "url": "http://swapi.co/api/people/1/"
}

https://swapi.co/api/people/1

Additional
round trips

When fetching complex, relational data structures: In order to descent further into the object graph, multiple round trips to the server are required.

{
    "name": "Tatooine", 
    "rotation_period": "23", 
    "orbital_period": "304", 
    "diameter": "10465", 
    "climate": "arid", 
    "gravity": "1 standard", 
    "terrain": "desert", 
    "surface_water": "1", 
    "population": "200000", 
    "residents": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/2/", 
        "http://swapi.co/api/people/4/", 
        "http://swapi.co/api/people/6/", 
        "http://swapi.co/api/people/7/", 
        "http://swapi.co/api/people/8/", 
        "http://swapi.co/api/people/9/", 
        "http://swapi.co/api/people/11/", 
        "http://swapi.co/api/people/43/", 
        "http://swapi.co/api/people/62/"
    ], 
    "films": [
        "http://swapi.co/api/films/5/", 
        "http://swapi.co/api/films/4/", 
        "http://swapi.co/api/films/6/", 
        "http://swapi.co/api/films/3/", 
        "http://swapi.co/api/films/1/"
    ], 
    "created": "2014-12-09T13:50:49.641000Z", 
    "edited": "2014-12-21T20:48:04.175778Z", 
    "url": "http://swapi.co/api/planets/1/"
}

https://swapi.co/api/planets/1

{
    "title": "A New Hope", 
    "episode_id": 4, 
    "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....", 
    "director": "George Lucas", 
    "producer": "Gary Kurtz, Rick McCallum", 
    "release_date": "1977-05-25", 
    "characters": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/2/", 
        "http://swapi.co/api/people/3/", 
        "http://swapi.co/api/people/4/", 
        "http://swapi.co/api/people/5/", 
        "http://swapi.co/api/people/6/", 
        "http://swapi.co/api/people/7/", 
        "http://swapi.co/api/people/8/", 
        "http://swapi.co/api/people/9/", 
        "http://swapi.co/api/people/10/", 
        "http://swapi.co/api/people/12/", 
        "http://swapi.co/api/people/13/", 
        "http://swapi.co/api/people/14/", 
        "http://swapi.co/api/people/15/", 
        "http://swapi.co/api/people/16/", 
        "http://swapi.co/api/people/18/", 
        "http://swapi.co/api/people/19/", 
        "http://swapi.co/api/people/81/"
    ], 
    "planets": [
        "http://swapi.co/api/planets/2/", 
        "http://swapi.co/api/planets/3/", 
        "http://swapi.co/api/planets/1/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/2/", 
        "http://swapi.co/api/starships/3/", 
        "http://swapi.co/api/starships/5/", 
        "http://swapi.co/api/starships/9/", 
        "http://swapi.co/api/starships/10/", 
        "http://swapi.co/api/starships/11/", 
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/13/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/4/", 
        "http://swapi.co/api/vehicles/6/", 
        "http://swapi.co/api/vehicles/7/", 
        "http://swapi.co/api/vehicles/8/"
    ], 
    "species": [
        "http://swapi.co/api/species/4/", 
        "http://swapi.co/api/species/5/", 
        "http://swapi.co/api/species/3/", 
        "http://swapi.co/api/species/2/", 
        "http://swapi.co/api/species/1/"
    ], 
    "created": "2014-12-10T14:23:31.880000Z", 
    "edited": "2015-04-11T09:46:52.774897Z", 
    "url": "http://swapi.co/api/films/1/"
}

https://swapi.co/api/films/1
https://swapi.co/api/films/2
https://swapi.co/api/films/3
https://swapi.co/api/films/6
https://swapi.co/api/films/7

{
    "title": "The Empire Strikes Back", 
    "episode_id": 5, 
    "opening_crawl": "It is a dark time for the\r\nRebellion. Although the Death\r\nStar has been destroyed,\r\nImperial troops have driven the\r\nRebel forces from their hidden\r\nbase and pursued them across\r\nthe galaxy.\r\n\r\nEvading the dreaded Imperial\r\nStarfleet, a group of freedom\r\nfighters led by Luke Skywalker\r\nhas established a new secret\r\nbase on the remote ice world\r\nof Hoth.\r\n\r\nThe evil lord Darth Vader,\r\nobsessed with finding young\r\nSkywalker, has dispatched\r\nthousands of remote probes into\r\nthe far reaches of space....", 
    "director": "Irvin Kershner", 
    "producer": "Gary Kutz, Rick McCallum", 
    "release_date": "1980-05-17", 
    "characters": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/2/", 
        "http://swapi.co/api/people/3/", 
        "http://swapi.co/api/people/4/", 
        "http://swapi.co/api/people/5/", 
        "http://swapi.co/api/people/10/", 
        "http://swapi.co/api/people/13/", 
        "http://swapi.co/api/people/14/", 
        "http://swapi.co/api/people/18/", 
        "http://swapi.co/api/people/20/", 
        "http://swapi.co/api/people/21/", 
        "http://swapi.co/api/people/22/", 
        "http://swapi.co/api/people/23/", 
        "http://swapi.co/api/people/24/", 
        "http://swapi.co/api/people/25/", 
        "http://swapi.co/api/people/26/"
    ], 
    "planets": [
        "http://swapi.co/api/planets/4/", 
        "http://swapi.co/api/planets/5/", 
        "http://swapi.co/api/planets/6/", 
        "http://swapi.co/api/planets/27/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/10/", 
        "http://swapi.co/api/starships/11/", 
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/15/", 
        "http://swapi.co/api/starships/21/", 
        "http://swapi.co/api/starships/22/", 
        "http://swapi.co/api/starships/23/", 
        "http://swapi.co/api/starships/3/", 
        "http://swapi.co/api/starships/17/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/8/", 
        "http://swapi.co/api/vehicles/14/", 
        "http://swapi.co/api/vehicles/16/", 
        "http://swapi.co/api/vehicles/18/", 
        "http://swapi.co/api/vehicles/19/", 
        "http://swapi.co/api/vehicles/20/"
    ], 
    "species": [
        "http://swapi.co/api/species/6/", 
        "http://swapi.co/api/species/7/", 
        "http://swapi.co/api/species/3/", 
        "http://swapi.co/api/species/2/", 
        "http://swapi.co/api/species/1/"
    ], 
    "created": "2014-12-12T11:26:24.656000Z", 
    "edited": "2015-04-11T09:46:31.433607Z", 
    "url": "http://swapi.co/api/films/2/"
}
{
    "title": "Revenge of the Sith", 
    "episode_id": 3, 
    "opening_crawl": "War! The Republic is crumbling\r\nunder attacks by the ruthless\r\nSith Lord, Count Dooku.\r\nThere are heroes on both sides.\r\nEvil is everywhere.\r\n\r\nIn a stunning move, the\r\nfiendish droid leader, General\r\nGrievous, has swept into the\r\nRepublic capital and kidnapped\r\nChancellor Palpatine, leader of\r\nthe Galactic Senate.\r\n\r\nAs the Separatist Droid Army\r\nattempts to flee the besieged\r\ncapital with their valuable\r\nhostage, two Jedi Knights lead a\r\ndesperate mission to rescue the\r\ncaptive Chancellor....", 
    "director": "George Lucas", 
    "producer": "Rick McCallum", 
    "release_date": "2005-05-19", 
    "characters": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/2/", 
        "http://swapi.co/api/people/3/", 
        "http://swapi.co/api/people/4/", 
        "http://swapi.co/api/people/5/", 
        "http://swapi.co/api/people/6/", 
        "http://swapi.co/api/people/7/", 
        "http://swapi.co/api/people/10/", 
        "http://swapi.co/api/people/11/", 
        "http://swapi.co/api/people/12/", 
        "http://swapi.co/api/people/13/", 
        "http://swapi.co/api/people/20/", 
        "http://swapi.co/api/people/21/", 
        "http://swapi.co/api/people/33/", 
        "http://swapi.co/api/people/35/", 
        "http://swapi.co/api/people/46/", 
        "http://swapi.co/api/people/51/", 
        "http://swapi.co/api/people/52/", 
        "http://swapi.co/api/people/53/", 
        "http://swapi.co/api/people/54/", 
        "http://swapi.co/api/people/55/", 
        "http://swapi.co/api/people/56/", 
        "http://swapi.co/api/people/58/", 
        "http://swapi.co/api/people/63/", 
        "http://swapi.co/api/people/64/", 
        "http://swapi.co/api/people/67/", 
        "http://swapi.co/api/people/68/", 
        "http://swapi.co/api/people/75/", 
        "http://swapi.co/api/people/78/", 
        "http://swapi.co/api/people/79/", 
        "http://swapi.co/api/people/80/", 
        "http://swapi.co/api/people/81/", 
        "http://swapi.co/api/people/82/", 
        "http://swapi.co/api/people/83/"
    ], 
    "planets": [
        "http://swapi.co/api/planets/2/", 
        "http://swapi.co/api/planets/5/", 
        "http://swapi.co/api/planets/8/", 
        "http://swapi.co/api/planets/9/", 
        "http://swapi.co/api/planets/12/", 
        "http://swapi.co/api/planets/13/", 
        "http://swapi.co/api/planets/14/", 
        "http://swapi.co/api/planets/15/", 
        "http://swapi.co/api/planets/16/", 
        "http://swapi.co/api/planets/17/", 
        "http://swapi.co/api/planets/18/", 
        "http://swapi.co/api/planets/19/", 
        "http://swapi.co/api/planets/1/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/48/", 
        "http://swapi.co/api/starships/59/", 
        "http://swapi.co/api/starships/61/", 
        "http://swapi.co/api/starships/32/", 
        "http://swapi.co/api/starships/63/", 
        "http://swapi.co/api/starships/64/", 
        "http://swapi.co/api/starships/65/", 
        "http://swapi.co/api/starships/66/", 
        "http://swapi.co/api/starships/68/", 
        "http://swapi.co/api/starships/74/", 
        "http://swapi.co/api/starships/75/", 
        "http://swapi.co/api/starships/2/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/33/", 
        "http://swapi.co/api/vehicles/50/", 
        "http://swapi.co/api/vehicles/60/", 
        "http://swapi.co/api/vehicles/62/", 
        "http://swapi.co/api/vehicles/67/", 
        "http://swapi.co/api/vehicles/69/", 
        "http://swapi.co/api/vehicles/70/", 
        "http://swapi.co/api/vehicles/71/", 
        "http://swapi.co/api/vehicles/72/", 
        "http://swapi.co/api/vehicles/73/", 
        "http://swapi.co/api/vehicles/76/", 
        "http://swapi.co/api/vehicles/53/", 
        "http://swapi.co/api/vehicles/56/"
    ], 
    "species": [
        "http://swapi.co/api/species/6/", 
        "http://swapi.co/api/species/15/", 
        "http://swapi.co/api/species/19/", 
        "http://swapi.co/api/species/20/", 
        "http://swapi.co/api/species/23/", 
        "http://swapi.co/api/species/24/", 
        "http://swapi.co/api/species/25/", 
        "http://swapi.co/api/species/26/", 
        "http://swapi.co/api/species/27/", 
        "http://swapi.co/api/species/28/", 
        "http://swapi.co/api/species/29/", 
        "http://swapi.co/api/species/30/", 
        "http://swapi.co/api/species/33/", 
        "http://swapi.co/api/species/34/", 
        "http://swapi.co/api/species/35/", 
        "http://swapi.co/api/species/36/", 
        "http://swapi.co/api/species/37/", 
        "http://swapi.co/api/species/3/", 
        "http://swapi.co/api/species/2/", 
        "http://swapi.co/api/species/1/"
    ], 
    "created": "2014-12-20T18:49:38.403000Z", 
    "edited": "2015-04-11T09:45:44.862122Z", 
    "url": "http://swapi.co/api/films/6/"
}
{
    "title": "The Force Awakens", 
    "episode_id": 7, 
    "opening_crawl": "Luke Skywalker has vanished.\r\nIn his absence, the sinister\r\nFIRST ORDER has risen from\r\nthe ashes of the Empire\r\nand will not rest until\r\nSkywalker, the last Jedi,\r\nhas been destroyed.\r\n \r\nWith the support of the\r\nREPUBLIC, General Leia Organa\r\nleads a brave RESISTANCE.\r\nShe is desperate to find her\r\nbrother Luke and gain his\r\nhelp in restoring peace and\r\njustice to the galaxy.\r\n \r\nLeia has sent her most daring\r\npilot on a secret mission\r\nto Jakku, where an old ally\r\nhas discovered a clue to\r\nLuke's whereabouts....", 
    "director": "J. J. Abrams", 
    "producer": "Kathleen Kennedy, J. J. Abrams, Bryan Burk", 
    "release_date": "2015-12-11", 
    "characters": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/3/", 
        "http://swapi.co/api/people/5/", 
        "http://swapi.co/api/people/13/", 
        "http://swapi.co/api/people/14/", 
        "http://swapi.co/api/people/27/", 
        "http://swapi.co/api/people/84/", 
        "http://swapi.co/api/people/85/", 
        "http://swapi.co/api/people/86/", 
        "http://swapi.co/api/people/87/", 
        "http://swapi.co/api/people/88/"
    ], 
    "planets": [
        "http://swapi.co/api/planets/61/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/77/", 
        "http://swapi.co/api/starships/10/"
    ], 
    "vehicles": [], 
    "species": [
        "http://swapi.co/api/species/1/", 
        "http://swapi.co/api/species/2/", 
        "http://swapi.co/api/species/3/"
    ], 
    "created": "2015-04-17T06:51:30.504780Z", 
    "edited": "2015-12-17T14:31:47.617768Z", 
    "url": "http://swapi.co/api/films/7/"
}
{
    "title": "Return of the Jedi", 
    "episode_id": 6, 
    "opening_crawl": "Luke Skywalker has returned to\r\nhis home planet of Tatooine in\r\nan attempt to rescue his\r\nfriend Han Solo from the\r\nclutches of the vile gangster\r\nJabba the Hutt.\r\n\r\nLittle does Luke know that the\r\nGALACTIC EMPIRE has secretly\r\nbegun construction on a new\r\narmored space station even\r\nmore powerful than the first\r\ndreaded Death Star.\r\n\r\nWhen completed, this ultimate\r\nweapon will spell certain doom\r\nfor the small band of rebels\r\nstruggling to restore freedom\r\nto the galaxy...", 
    "director": "Richard Marquand", 
    "producer": "Howard G. Kazanjian, George Lucas, Rick McCallum", 
    "release_date": "1983-05-25", 
    "characters": [
        "http://swapi.co/api/people/1/", 
        "http://swapi.co/api/people/2/", 
        "http://swapi.co/api/people/3/", 
        "http://swapi.co/api/people/4/", 
        "http://swapi.co/api/people/5/", 
        "http://swapi.co/api/people/10/", 
        "http://swapi.co/api/people/13/", 
        "http://swapi.co/api/people/14/", 
        "http://swapi.co/api/people/45/", 
        "http://swapi.co/api/people/16/", 
        "http://swapi.co/api/people/18/", 
        "http://swapi.co/api/people/20/", 
        "http://swapi.co/api/people/21/", 
        "http://swapi.co/api/people/22/", 
        "http://swapi.co/api/people/25/", 
        "http://swapi.co/api/people/27/", 
        "http://swapi.co/api/people/28/", 
        "http://swapi.co/api/people/29/", 
        "http://swapi.co/api/people/30/", 
        "http://swapi.co/api/people/31/"
    ], 
    "planets": [
        "http://swapi.co/api/planets/5/", 
        "http://swapi.co/api/planets/7/", 
        "http://swapi.co/api/planets/8/", 
        "http://swapi.co/api/planets/9/", 
        "http://swapi.co/api/planets/1/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/10/", 
        "http://swapi.co/api/starships/11/", 
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/15/", 
        "http://swapi.co/api/starships/22/", 
        "http://swapi.co/api/starships/23/", 
        "http://swapi.co/api/starships/27/", 
        "http://swapi.co/api/starships/28/", 
        "http://swapi.co/api/starships/29/", 
        "http://swapi.co/api/starships/3/", 
        "http://swapi.co/api/starships/17/", 
        "http://swapi.co/api/starships/2/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/8/", 
        "http://swapi.co/api/vehicles/16/", 
        "http://swapi.co/api/vehicles/18/", 
        "http://swapi.co/api/vehicles/19/", 
        "http://swapi.co/api/vehicles/24/", 
        "http://swapi.co/api/vehicles/25/", 
        "http://swapi.co/api/vehicles/26/", 
        "http://swapi.co/api/vehicles/30/"
    ], 
    "species": [
        "http://swapi.co/api/species/5/", 
        "http://swapi.co/api/species/6/", 
        "http://swapi.co/api/species/8/", 
        "http://swapi.co/api/species/9/", 
        "http://swapi.co/api/species/10/", 
        "http://swapi.co/api/species/15/", 
        "http://swapi.co/api/species/3/", 
        "http://swapi.co/api/species/2/", 
        "http://swapi.co/api/species/1/"
    ], 
    "created": "2014-12-18T10:39:33.255000Z", 
    "edited": "2015-04-11T09:46:05.220365Z", 
    "url": "http://swapi.co/api/films/3/"
}

Alright, let's fix this

http://swapi.co/api/people/1?
with=homeworld.name,film.name

http://swapi.co/api/people-with-homeworld-and-films

http://swapi.co/api/people-with-association-names

Endpoints galore

Attempting to circumvent the aforementioned problems often leads to a huge amount of bespoke/ad-hoc endpoints. This, in turn, inevitably increases the complexity of your application.

So far, we haven't even talked about versioning and backwards compatibility!

„This is not not how we, as product developers, think about data. Product developers think of data in terms of graphs.“

Subtitle

Nick Schrock

Wouldn't it be great if ...

What is this sorcery?

Tokenizer and parser for the GraphQL language specifiction

A Data querying language ...

Running on arbitrary code ...

Backed by a schema ...

Based on a type system ...

Making it fully introspective ...

Not a query language for a graph database.

It runs on arbitrary code.

It is completely agnostic of your storage layer and can potentially support any backend architecture.

Evolving the server–client relationship

The server publishes its possibilities; the client specifies its requirements.

Learning GraphQL

For the adventurous:
http://graphql-swapi.parseapp.com/


For the tourists:
https://learngraphql.com/


For the brave:
https://facebook.github.io/graphql/

Let's see it in action

Queries

Mutations

Fragments

Directives

Variables

Arguments

Sub selections

Aliasing

Subscriptions

Time for a demo

Schema

Each schema is an arbitrarily nested hierarchy of type definitions.

Type system

Scalars
Objects
Interfaces
Unions

interface EntityNode {
  title: String
  uid: EntityFieldNodeUser
  created: Int!
  updated: Int!
  nid: Id!
  ...
}

type EntityNodeArticle implements EntityNode {
  body: String
  tags: [EntityFieldTags]
  ...
}

type EntityNodePage implements EntityNode {
  body: String
  ...
}
type EntityNodeArticle {
  ...
  fields: {
    title: {
      type: String
      resolve: function (entity) {
        return entity.title
      }
    }
    image: {
      type: EntityNodeFieldImage
      args: {
        formatter: {
          type: EntityNodeFieldImageFormattersEnum!
        }
      }
      resolve: function (entity, args) {
        return format(entity.image, args.formatter)
      }
    }
  }
  ...
}

Introspection

Exposing your own schema through the type system

Do you remember the movie "Inception"?

Time for a demo

Building a GraphQL Server

GraphQL

Your application code

Database

Internal APIs

Remote APIs

What about Drupal?

Exposing your entire Drupal data graph through a fully generated, self-documenting schema.

The goal

Make the resulting graph accessible through single and batch loading of entities using the EntityQuery API.

The goal

TypedData API

We've got the ideal foundation

... Translate TypedData definitions into GraphQL Types and compose them in a GraphQL Schema.

The module's job is to ...

Initial version of the module has been released recently.

Time for a demo

Limitations

Not Relay compliant
No Mutations
No Config Entities

Outlook

Relay compliance
Mutation support
Config entity support
Schema customization
Query routes and obfuscation
 

Bonus

Questions

Resources

  • RFC Working Draft: https://facebook.github.io/graphql/
  • Reference implemenetation: https://github.com/graphql/graphql-js
  • GraphiQL: https://github.com/graphql/graphiql
  • StarWars API Playground: http://graphql-swapi.parseapp.com/
  • Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
  • Learn GraphQL: https://learngraphql.com/
  • GraphQL & Relay Drupal Demo: https://github.com/fubhy/drupal-decoupled-app

 

GraphQL Meets Drupal - DrupalCon NOLA 2016

By Sebastian Siemssen

GraphQL Meets Drupal - DrupalCon NOLA 2016

  • 1,593