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. What is GraphQL? •https://graphql.org •“GraphQL is a query language for

    your API.” •Facebook circa 2012, open-sourced circa 2015
  2. What did we want from REST? •More flexibility — lots

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

    of schema revisions •Speed in Development •Fewer requests
  4. [ { "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" ] } ]
  5. 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
  6. Installation Note •Download the module (or use drush dl graphql)

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

    •Run composer require webonyx/graphql-php at root level (error otherwise) •Enable the module
  8. 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}}}
  9. Queries: Alias •This allows you to rename stuff query {

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

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

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

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

    nodeQuery { entities { node_id: entityId, title: entityLabel } } }
  14. Queries: Alias query { ship1: nodeById(id: "5") { entityLabel }

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

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

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

    } } fragment nodeBasics on Node { entityId entityType entityLabel }
  18. Queries: Fragments •DRYing out your queries using fragments can make

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

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

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

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

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

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

    { entities { ...on NodeVessel { entityId, entityLabel, slides: fieldVSlides { slide: entity { fieldDisplayTitle { value } } } } } } }
  25. 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" } } },
  26. Queries: Filters •Usually node type query { nodeQuery(filter:{conditions: [{field: "type",

    value: "vessel"}]}) { entities { entityId, entityLabel } } }
  27. 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" },
  28. Custom Types: Adding Fields to Types { nodeQuery(filter: {conditions: [{field:

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

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

    "type", value: "vessel", operator: EQUAL}]}) { entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url } } } } } } }
  31. 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" } } } },
  32. 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; } } }
  33. 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" * )
  34. 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" * )
  35. 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" * )
  36. 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; } }
  37. { nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {

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

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

    entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url, relative_path } } } } } } }
  40. Custom Types: Settings Form •A node is in the core

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

    entity types. But what about stuff that isn’t? •Settings example
  42. 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); } }
  43. 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" * }, * ) */
  44. 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" }" ] } }
  45. 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