Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rapid Data APIs using GraphQL and Drupal

Rapid Data APIs using GraphQL and Drupal

A quick introduction to using GraphQL with Drupal 8.

dirtystylus

May 10, 2019
Tweet

More Decks by dirtystylus

Other Decks in Programming

Transcript

  1. Rapid Data APIs using
    GraphQL and Drupal
    Drupaldelphia 2019

    View Slide

  2. Hi.
    Mark Llobrera
    Technology Director, Bluecadet Philadelphia
    @dirtystylus
    @bluecadet

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. Descriptive, Not Prescriptive
    Share experiences, not solutions.

    View Slide

  8. What is GraphQL?

    View Slide

  9. https://twitter.com/sarahmei/status/991878504391229440/photo/1

    View Slide

  10. What is GraphQL?

    View Slide

  11. What is GraphQL?
    •https://graphql.org

    View Slide

  12. What is GraphQL?
    •https://graphql.org
    •“GraphQL is a query language for your API.”

    View Slide

  13. What is GraphQL?
    •https://graphql.org
    •“GraphQL is a query language for your API.”
    •Facebook circa 2012, open-sourced circa 2015

    View Slide

  14. Where We’ve Been

    View Slide

  15. Where We’ve Been
    •REST (ish)

    View Slide

  16. Where We’ve Been
    •REST (ish)
    •JSON API

    View Slide

  17. What did we want from REST?

    View Slide

  18. What did we want from REST?
    •More flexibility — lots of schema revisions

    View Slide

  19. What did we want from REST?
    •More flexibility — lots of schema revisions
    •Speed in Development

    View Slide

  20. What did we want from REST?
    •More flexibility — lots of schema revisions
    •Speed in Development
    •Fewer requests

    View Slide

  21. [
    {
    "title": "Kim Stanley Robinson",
    "body": "Author of the Mars trilogy and New York 2140.\r\n",
    "field_books": [
    "2"
    ]
    },
    {
    "title": "N. K. Jemisin",
    "body": "Hugo Award-winning author of the Broken Earth trilogy.\r\n",
    "field_books": [
    "1"
    ]
    }
    ]

    View Slide

  22. Use Cases for GraphQL (Bluecadet)

    View Slide

  23. Use Cases for GraphQL (Bluecadet)
    •Multi-product projects

    View Slide

  24. Use Cases for GraphQL (Bluecadet)
    •Multi-product projects
    •API to be used by (un)known future products

    View Slide

  25. Use Cases for GraphQL (Bluecadet)
    •Multi-product projects
    •API to be used by (un)known future products
    •If your teams are not in tight communication

    View Slide

  26. Drupal Implementation

    View Slide

  27. Drupal Implementation
    •https://www.drupal.org/project/graphql

    View Slide

  28. Drupal Implementation
    •https://www.drupal.org/project/graphql
    •https://github.com/drupal-graphql/graphql

    View Slide

  29. Drupal Implementation
    •https://www.drupal.org/project/graphql
    •https://github.com/drupal-graphql/graphql
    •Drupal has schema support for Nodes, Views, etc

    View Slide

  30. Drupal Implementation
    •https://www.drupal.org/project/graphql
    •https://github.com/drupal-graphql/graphql
    •Drupal has schema support for Nodes, Views, etc
    •Plugin architecture

    View Slide

  31. Installation Note

    View Slide

  32. Installation Note
    •Download the module (or use drush dl graphql)

    View Slide

  33. Installation Note
    •Download the module (or use drush dl graphql)
    •Run composer require webonyx/graphql-php at
    root level (error otherwise)

    View Slide

  34. Installation Note
    •Download the module (or use drush dl graphql)
    •Run composer require webonyx/graphql-php at
    root level (error otherwise)
    •Enable the module

    View Slide

  35. GraphQL Basics

    View Slide

  36. Queries
    query {
    nodeQuery {
    entities {
    entityId,
    entityLabel
    }
    }
    }

    View Slide

  37. Queries
    /graphql/?query=query%20{nodeQuery%20{entities%20{entityId,entityLabel}}}

    View Slide

  38. Queries
    •You can run this in the GraphQL Explorer
    /graphql/?query=query%20{nodeQuery%20{entities%20{entityId,entityLabel}}}

    View Slide

  39. Queries
    •You can run this in the GraphQL Explorer
    •What about the browser? You can copy the query and
    pass it to the /graphql endpoint with the query
    parameter:
    /graphql/?query=query%20{nodeQuery%20{entities%20{entityId,entityLabel}}}

    View Slide

  40. View Slide

  41. View Slide

  42. Queries: Alias
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  43. Queries: Alias
    •This allows you to rename stuff
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  44. Queries: Alias
    •This allows you to rename stuff
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  45. Queries: Alias
    •This allows you to rename stuff
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  46. Queries: Alias
    •This allows you to rename stuff
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  47. Queries: Alias
    •This allows you to rename stuff
    query {
    nodeQuery {
    entities {
    node_id: entityId,
    title: entityLabel
    }
    }
    }

    View Slide

  48. Queries: Alias
    {
    "node_id": "26",
    "title": "The Herreshoff Manufacturing Company is officially established."
    },

    View Slide

  49. Queries: Alias
    {
    "entityId": "26",
    "entityLabel": "The Herreshoff Manufacturing Company is officially established."
    },

    View Slide

  50. Queries: Alias
    query {
    ship1: nodeById(id: "5") {
    entityLabel
    }
    ship2:nodeById(id: "7") {
    entityLabel
    }
    }

    View Slide

  51. Queries: Alias
    •Also allows you to run query twice
    query {
    ship1: nodeById(id: "5") {
    entityLabel
    }
    ship2:nodeById(id: "7") {
    entityLabel
    }
    }

    View Slide

  52. Queries: Alias
    {
    "data": {
    "ship1": {
    "entityLabel": "Reliance - America's Cup Defender"
    },
    "ship2": {
    "entityLabel": "Alera - An Icon of One-Design Racing"
    }
    }
    }

    View Slide

  53. Queries: Fragments
    query {
    nodeQuery {
    entities {
    entityId
    entityType
    entityLabel
    }
    }
    }

    View Slide

  54. Queries: Fragments
    query {
    nodeQuery {
    entities {
    ...nodeBasics
    }
    }
    }
    fragment nodeBasics on Node {
    entityId
    entityType
    entityLabel
    }

    View Slide

  55. Queries: Fragments

    View Slide

  56. Queries: Fragments
    •DRYing out your queries using fragments can make it
    easier to keep track of common structures

    View Slide

  57. Queries: Arguments
    query getShip($nid: String = "") {
    nodeById(id:$nid) {
    entityId
    entityType
    entityLabel
    }
    }
    {"nid": "5"}

    View Slide

  58. Queries: Arguments
    •query
    query getShip($nid: String = "") {
    nodeById(id:$nid) {
    entityId
    entityType
    entityLabel
    }
    }
    {"nid": "5"}

    View Slide

  59. Queries: Arguments
    •query
    query getShip($nid: String = "") {
    nodeById(id:$nid) {
    entityId
    entityType
    entityLabel
    }
    }
    {"nid": "5"}

    View Slide

  60. Queries: Arguments
    •query
    query getShip($nid: String = "") {
    nodeById(id:$nid) {
    entityId
    entityType
    entityLabel
    }
    }
    •variable
    {"nid": "5"}

    View Slide

  61. Queries: Arguments
    {
    "data": {
    "nodeById": {
    "entityId": "5",
    "entityType": "node",
    "entityLabel": "Reliance - America's Cup Defender"
    }
    }
    }

    View Slide

  62. Queries: Referenced Entities
    query {
    nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
    entities {
    ...on NodeVessel {
    entityId,
    entityLabel,
    slides: fieldVSlides {
    slide: entity {
    fieldDisplayTitle {
    value
    }
    }
    }
    }
    }
    }
    }

    View Slide

  63. Queries: Referenced Entities
    query {
    nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
    entities {
    ...on NodeVessel {
    entityId,
    entityLabel,
    slides: fieldVSlides {
    slide: entity {
    fieldDisplayTitle {
    value
    }
    }
    }
    }
    }
    }
    }

    View Slide

  64. Queries: Referenced Entities
    query {
    nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
    entities {
    ...on NodeVessel {
    entityId,
    entityLabel,
    slides: fieldVSlides {
    slide: entity {
    fieldDisplayTitle {
    value
    }
    }
    }
    }
    }
    }
    }

    View Slide

  65. Queries: Referenced Entities
    "entities": [
    {
    "entityId": "5",
    "entityLabel": "Reliance - America's Cup Defender",
    "slides": [
    {
    "slide": {
    "fieldDisplayTitle": {
    "value": "Pushing The Limits of Design And Engineering"
    }
    }
    },
    {
    "slide": {
    "fieldDisplayTitle": {
    "value": "Pushing the Bounds to Win"
    }
    }
    },
    {
    "slide": {
    "fieldDisplayTitle": {
    "value": "Pushing the Bounds to Win"
    }
    }
    },

    View Slide

  66. Queries: Filters
    query {
    nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
    entities {
    entityId,
    entityLabel
    }
    }
    }

    View Slide

  67. Queries: Filters
    •Usually node type
    query {
    nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
    entities {
    entityId,
    entityLabel
    }
    }
    }

    View Slide

  68. Queries: Filters
    {
    "entityId": "5",
    "entityLabel": "Reliance - America's Cup Defender"
    },
    {
    "entityId": "43",
    "entityLabel": "Cushing"
    },
    {
    "entityId": "40",
    "entityLabel": "Asahi"
    },
    {
    "entityId": "8",
    "entityLabel": "Buzzards Bay- Fast, Agile & Affordable"
    },

    View Slide

  69. Slightly more advanced

    View Slide

  70. Custom Types: Adding Fields to Types
    {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url
    }
    }
    }
    }
    }
    }
    }

    View Slide

  71. Custom Types: Adding Fields to Types
    {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url
    }
    }
    }
    }
    }
    }
    }

    View Slide

  72. Custom Types: Adding Fields to Types
    {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url
    }
    }
    }
    }
    }
    }
    }

    View Slide

  73. Custom Types: Adding Fields to Types
    {
    "entityId": "5",
    "title": "Reliance - America's Cup Defender",
    "fieldTimelineThumbnail": {
    "entity": {
    "fieldMediaImage": {
    "url": "https://mit-lsf-cms.ml/sites/default/files/images/Reliance_1.jpg"
    }
    }
    }
    },

    View Slide

  74. Custom Types: Adding Fields to Types
    (Annotations)
    namespace Drupal\mit_graphql\Plugin\GraphQL\Fields\Entity\Fields\Image;
    use Drupal\graphql\GraphQL\Execution\ResolveContext;
    use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
    use Drupal\image\Plugin\Field\FieldType\ImageItem;
    use GraphQL\Type\Definition\ResolveInfo;
    /**
    * Retrieve the image relative path.
    *
    * @GraphQLField(
    * id = "image_relative_path",
    * secure = true,
    * name = "relative_path",
    * type = "String",
    * field_types = {"image"},
    * provider = "image",
    * deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityFieldPropertyDeriver"
    * )
    */
    class ImageRelativePath extends FieldPluginBase {
    /**
    * {@inheritdoc}
    */
    protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    if ($value instanceof ImageItem && $value->entity->access('view')) {
    $url = file_create_url($value->entity->getFileUri());
    $relative_path = urldecode(file_url_transform_relative($url));
    $replaced_relative_path = str_replace('/sites/default/files', '', $relative_path);
    yield $replaced_relative_path;
    }
    }
    }

    View Slide

  75. Custom Types: Adding Fields to Types
    (Annotations)
    * @GraphQLField(
    * id = "image_relative_path",
    * secure = true,
    * name = "relative_path",
    * type = "String",
    * field_types = {"image"},
    * provider = "image",
    * deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityFieldPropertyDeriver"
    * )

    View Slide

  76. Custom Types: Adding Fields to Types
    (Annotations)
    * @GraphQLField(
    * id = "image_relative_path",
    * secure = true,
    * name = "relative_path",
    * type = "String",
    * field_types = {"image"},
    * provider = "image",
    * deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityFieldPropertyDeriver"
    * )

    View Slide

  77. Custom Types: Adding Fields to Types
    (Annotations)
    * @GraphQLField(
    * id = "image_relative_path",
    * secure = true,
    * name = "relative_path",
    * type = "String",
    * field_types = {"image"},
    * provider = "image",
    * deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityFieldPropertyDeriver"
    * )

    View Slide

  78. Custom Types: Adding Fields to Types
    (Annotations)
    protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    if ($value instanceof ImageItem && $value->entity->access('view')) {
    $url = file_create_url($value->entity->getFileUri());
    $relative_path = urldecode(file_url_transform_relative($url));
    $replaced_relative_path = str_replace('/sites/default/files', '', $relative_path);
    yield $replaced_relative_path;
    }
    }

    View Slide

  79. {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url,
    relative_path
    }
    }
    }
    }
    }
    }
    }

    View Slide

  80. {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url,
    relative_path
    }
    }
    }
    }
    }
    }
    }

    View Slide

  81. {
    nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
    entities {
    ... on NodeVessel {
    entityId,
    title,
    fieldTimelineThumbnail {
    entity {
    fieldMediaImage {
    url,
    relative_path
    }
    }
    }
    }
    }
    }
    }

    View Slide

  82. What about documentation?

    View Slide

  83. For examples:
    graphql/tests/modules/graphql_plugin_test/src/Plugin/GraphQL/Fields
    Annotations:
    https://www.drupal.org/docs/8/api/plugin-api/
    annotations-based-plugins

    View Slide

  84. Custom Types: Settings Form

    View Slide

  85. Custom Types: Settings Form
    •A node is in the core entity types. But what about stuff
    that isn’t?

    View Slide

  86. Custom Types: Settings Form
    •A node is in the core entity types. But what about stuff
    that isn’t?
    •Settings example

    View Slide

  87. Custom Types: Settings Form

    View Slide

  88. Custom Types: Settings Form
    •Basic custom form for saving to Drupal::state()

    View Slide

  89. Custom Types: Settings Form
    namespace Drupal\thf_graphql\Plugin\GraphQL\Fields;
    use Drupal\graphql\GraphQL\Execution\ResolveContext;
    use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
    use GraphQL\Type\Definition\ResolveInfo;
    use Symfony\Component\HttpFoundation\Response;
    /**
    * A JSON Payload of the custom settings object.ResolveInfo
    *
    * @GraphQLField(
    * id = "payload",
    * secure = true,
    * name = "Payload",
    * type = "string",
    * multi = true,
    * arguments = {
    * "settings_name" = "String"
    * },
    * )
    */
    class Payload extends FieldPluginBase {
    /**
    * {@inheritdoc}
    */
    public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    $settings = \Drupal::state()->get($args['settings_name'], []);
    yield json_encode($settings);
    }
    }

    View Slide

  90. Custom Types: Settings Form
    /**
    * A JSON Payload of the custom settings object.ResolveInfo
    *
    * @GraphQLField(
    * id = "payload",
    * secure = true,
    * name = "Payload",
    * type = "string",
    * multi = true,
    * arguments = {
    * "settings_name" = "String"
    * },
    * )
    */

    View Slide

  91. Custom Types: Settings Form
    query{
    Payload(settings_name:"thf_utility.gcib_settings")
    }

    View Slide

  92. Custom Types: Settings Form
    {
    "data": {
    "Payload": [
    "{
    "text_cta": "See More",
    "story_cta": "Read the Story",
    "scroll_wheel_cta": "Continue",
    "lens_cta": "View More",
    "draw_cta": "Draw Now",
    "quiz_cta": "Take the Quiz"
    }"
    ]
    }
    }

    View Slide

  93. Case Study

    View Slide

  94. Case Study
    •Two products

    View Slide

  95. Case Study
    •Two products
    •One Cinder touchscreen

    View Slide

  96. Case Study
    •Two products
    •One Cinder touchscreen
    •One JS touchscreen

    View Slide

  97. View Slide

  98. What did we learn?

    View Slide

  99. What did we learn?
    •Empty structures/values break Cinder

    View Slide

  100. What did we learn?
    •Empty structures/values break Cinder
    •Making queries consistent is a challenge

    View Slide

  101. What did we learn?
    •Empty structures/values break Cinder
    •Making queries consistent is a challenge
    •Decoupling is both a technical thing as well as a
    philosophical thing

    View Slide

  102. DEMO TIME

    View Slide

  103. View Slide

  104. View Slide

  105. We’re
    hiring.

    View Slide

  106. View Slide

  107. THANK YOU.
    Mark Llobrera
    Technology Director, Bluecadet Philadelphia
    https://speakerdeck.com/mllobrera
    @dirtystylus
    @bluecadet

    View Slide