Rapid Data APIs using GraphQL and Drupal

Rapid Data APIs using GraphQL and Drupal

A quick introduction to using GraphQL with Drupal 8.

D74c365206652832a56fd9ba1fb61d99?s=128

dirtystylus

May 10, 2019
Tweet

Transcript

  1. Rapid Data APIs using GraphQL and Drupal Drupaldelphia 2019

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

  3. None
  4. None
  5. None
  6. None
  7. Descriptive, Not Prescriptive Share experiences, not solutions.

  8. What is GraphQL?

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

  10. What is GraphQL?

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

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

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

    your API.” •Facebook circa 2012, open-sourced circa 2015
  14. Where We’ve Been

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

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

  17. What did we want from REST?

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

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

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

    of schema revisions •Speed in Development •Fewer requests
  21. [ { "title": "Kim Stanley Robinson", "body": "<p>Author of the

    <em>Mars</em> trilogy and <em>New York 2140</em>.</p>\r\n", "field_books": [ "2" ] }, { "title": "N. K. Jemisin", "body": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n", "field_books": [ "1" ] } ]
  22. Use Cases for GraphQL (Bluecadet)

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

  24. Use Cases for GraphQL (Bluecadet) •Multi-product projects •API to be

    used by (un)known future products
  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
  26. Drupal Implementation

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

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

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

    Views, etc
  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
  31. Installation Note

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

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

    •Run composer require webonyx/graphql-php at root level (error otherwise)
  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
  35. GraphQL Basics

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

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

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

  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}}}
  40. None
  41. None
  42. Queries: Alias query { nodeQuery { entities { node_id: entityId,

    title: entityLabel } } }
  43. Queries: Alias •This allows you to rename stuff query {

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

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

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

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

    nodeQuery { entities { node_id: entityId, title: entityLabel } } }
  48. Queries: Alias { "node_id": "26", "title": "The Herreshoff Manufacturing Company

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

    is officially established." },
  50. Queries: Alias query { ship1: nodeById(id: "5") { entityLabel }

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

    { ship1: nodeById(id: "5") { entityLabel } ship2:nodeById(id: "7") { entityLabel } }
  52. Queries: Alias { "data": { "ship1": { "entityLabel": "Reliance -

    America's Cup Defender" }, "ship2": { "entityLabel": "Alera - An Icon of One-Design Racing" } } }
  53. Queries: Fragments query { nodeQuery { entities { entityId entityType

    entityLabel } } }
  54. Queries: Fragments query { nodeQuery { entities { ...nodeBasics }

    } } fragment nodeBasics on Node { entityId entityType entityLabel }
  55. Queries: Fragments

  56. Queries: Fragments •DRYing out your queries using fragments can make

    it easier to keep track of common structures
  57. Queries: Arguments query getShip($nid: String = "") { nodeById(id:$nid) {

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

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

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

    { entityId entityType entityLabel } } •variable {"nid": "5"}
  61. Queries: Arguments { "data": { "nodeById": { "entityId": "5", "entityType":

    "node", "entityLabel": "Reliance - America's Cup Defender" } } }
  62. Queries: Referenced Entities query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]})

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

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

    { entities { ...on NodeVessel { entityId, entityLabel, slides: fieldVSlides { slide: entity { fieldDisplayTitle { value } } } } } } }
  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" } } },
  66. Queries: Filters query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {

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

    value: "vessel"}]}) { entities { entityId, entityLabel } } }
  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" },
  69. Slightly more advanced

  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 } } } } } } }
  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 } } } } } } }
  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 } } } } } } }
  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" } } } },
  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; } } }
  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" * )
  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" * )
  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" * )
  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; } }
  79. { nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {

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

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

    entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url, relative_path } } } } } } }
  82. What about documentation?

  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

  84. Custom Types: Settings Form

  85. Custom Types: Settings Form •A node is in the core

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

    entity types. But what about stuff that isn’t? •Settings example
  87. Custom Types: Settings Form

  88. Custom Types: Settings Form •Basic custom form for saving to

    Drupal::state()
  89. Custom Types: Settings Form <?php 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); } }
  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" * }, * ) */
  91. Custom Types: Settings Form query{ Payload(settings_name:"thf_utility.gcib_settings") }

  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" }" ] } }
  93. Case Study

  94. Case Study •Two products

  95. Case Study •Two products •One Cinder touchscreen

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

  97. None
  98. What did we learn?

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

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

    consistent is a challenge
  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
  102. DEMO TIME

  103. None
  104. None
  105. We’re hiring.

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

    @bluecadet