Slide 1

Slide 1 text

Rapid Data APIs using GraphQL and Drupal Drupaldelphia 2019

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Descriptive, Not Prescriptive Share experiences, not solutions.

Slide 8

Slide 8 text

What is GraphQL?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

What is GraphQL?

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Where We’ve Been

Slide 15

Slide 15 text

Where We’ve Been •REST (ish)

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

What did we want from REST?

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

[ { "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" ] } ]

Slide 22

Slide 22 text

Use Cases for GraphQL (Bluecadet)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Drupal Implementation

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Installation Note

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

GraphQL Basics

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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}}}

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Queries: Fragments

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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" } } },

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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" },

Slide 69

Slide 69 text

Slightly more advanced

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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" } } } },

Slide 74

Slide 74 text

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; } } }

Slide 75

Slide 75 text

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" * )

Slide 76

Slide 76 text

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" * )

Slide 77

Slide 77 text

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" * )

Slide 78

Slide 78 text

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; } }

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

What about documentation?

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

Custom Types: Settings Form

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Custom Types: Settings Form

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

Custom Types: Settings Form get($args['settings_name'], []); yield json_encode($settings); } }

Slide 90

Slide 90 text

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" * }, * ) */

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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" }" ] } }

Slide 93

Slide 93 text

Case Study

Slide 94

Slide 94 text

Case Study •Two products

Slide 95

Slide 95 text

Case Study •Two products •One Cinder touchscreen

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

What did we learn?

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

DEMO TIME

Slide 103

Slide 103 text

No content

Slide 104

Slide 104 text

No content

Slide 105

Slide 105 text

We’re hiring.

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

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