Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Rapid Data APIs using GraphQL and Drupal
Search
dirtystylus
May 10, 2019
Programming
0
60
Rapid Data APIs using GraphQL and Drupal
A quick introduction to using GraphQL with Drupal 8.
dirtystylus
May 10, 2019
Tweet
Share
More Decks by dirtystylus
See All by dirtystylus
Decoupled Drupal Days 2018: Drupal Unhitched
mllobrera
0
310
Drupaldelphia 2018: Drupal Unhitched: The CMS in Decoupled Architectures
mllobrera
0
250
Drupalcon 2017: Pattern Language: Pattern Libraries in the Wild
mllobrera
0
450
DIW 2016: Github Project Scaffolding with Google Sheets and Zenhub
mllobrera
0
220
Philly Tech Week 2016: Decoupled Development: Feeding Your Application with Off-the-Shelf Tools
mllobrera
0
140
Drupal Beyond the Browser: Using Drupal to Power Apps and Touchscreens
mllobrera
0
350
Decoupled Development with WordPress JSON APIs
mllobrera
0
690
Drupaldelphia 2014: Progressive Enhancement in Drupal 7, Using Ajax-Include Patterns
mllobrera
0
150
Drupaldelphia 2013 - Tyler School of Art: A Case Study in User-Centered Decision-Making
mllobrera
0
1k
Other Decks in Programming
See All in Programming
2度もゼロから書き直して、やっとブラウザでぬるぬる動くAIに辿り着いた話
tomoino
0
110
TSConfig Solution Style & subpath imports to switch types on a per-file basis
maminami373
1
190
少数精鋭エンジニアがフルスタック力を磨く理由 -そしてAI時代へ-
rebase_engineering
0
140
External SecretsのさくらProvider初期実装を担当しています
logica0419
0
250
「兵法」から見る質とスピード
ickx
0
230
AIエージェントによるテストフレームワーク Arbigent
takahirom
0
300
💎 My RubyKaigi Effect in 2025: Top Ruby Companies 🌐
yasulab
PRO
1
130
"使いづらい" をリバースエンジニアリングする UI の読み解き方
rebase_engineering
0
110
コードに語らせよう――自己ドキュメント化が内包する楽しさについて / Let the Code Speak
nrslib
5
1.1k
TypeScript製IaCツールのAWS CDKが様々な言語で実装できる理由 ~他言語変換の仕組み~ / cdk-language-transformation
gotok365
7
400
実践ArchUnit ~実例による検証パターンの紹介~
ogiwarat
1
120
プロダクト改善のために新しいことを始める -useContextからの卒業、Zustandへ-
rebase_engineering
1
100
Featured
See All Featured
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
180
53k
[RailsConf 2023] Rails as a piece of cake
palkan
55
5.6k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
19
1.3k
Six Lessons from altMBA
skipperchong
28
3.8k
Bash Introduction
62gerente
614
210k
Music & Morning Musume
bryan
47
6.6k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
Designing for Performance
lara
608
69k
RailsConf 2023
tenderlove
30
1.1k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.7k
Transcript
Rapid Data APIs using GraphQL and Drupal Drupaldelphia 2019
Hi. Mark Llobrera Technology Director, Bluecadet Philadelphia @dirtystylus @bluecadet
None
None
None
None
Descriptive, Not Prescriptive Share experiences, not solutions.
What is GraphQL?
https://twitter.com/sarahmei/status/991878504391229440/photo/1
What is GraphQL?
What is GraphQL? •https://graphql.org
What is GraphQL? •https://graphql.org •“GraphQL is a query language for
your API.”
What is GraphQL? •https://graphql.org •“GraphQL is a query language for
your API.” •Facebook circa 2012, open-sourced circa 2015
Where We’ve Been
Where We’ve Been •REST (ish)
Where We’ve Been •REST (ish) •JSON API
What did we want from REST?
What did we want from REST? •More flexibility — lots
of schema revisions
What did we want from REST? •More flexibility — lots
of schema revisions •Speed in Development
What did we want from REST? •More flexibility — lots
of schema revisions •Speed in Development •Fewer requests
[ { "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" ] } ]
Use Cases for GraphQL (Bluecadet)
Use Cases for GraphQL (Bluecadet) •Multi-product projects
Use Cases for GraphQL (Bluecadet) •Multi-product projects •API to be
used by (un)known future products
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
Drupal Implementation
Drupal Implementation •https://www.drupal.org/project/graphql
Drupal Implementation •https://www.drupal.org/project/graphql •https://github.com/drupal-graphql/graphql
Drupal Implementation •https://www.drupal.org/project/graphql •https://github.com/drupal-graphql/graphql •Drupal has schema support for Nodes,
Views, etc
Drupal Implementation •https://www.drupal.org/project/graphql •https://github.com/drupal-graphql/graphql •Drupal has schema support for Nodes,
Views, etc •Plugin architecture
Installation Note
Installation Note •Download the module (or use drush dl graphql)
Installation Note •Download the module (or use drush dl graphql)
•Run composer require webonyx/graphql-php at root level (error otherwise)
Installation Note •Download the module (or use drush dl graphql)
•Run composer require webonyx/graphql-php at root level (error otherwise) •Enable the module
GraphQL Basics
Queries query { nodeQuery { entities { entityId, entityLabel }
} }
Queries /graphql/?query=query%20{nodeQuery%20{entities%20{entityId,entityLabel}}}
Queries •You can run this in the GraphQL Explorer /graphql/?query=query%20{nodeQuery%20{entities%20{entityId,entityLabel}}}
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}}}
None
None
Queries: Alias query { nodeQuery { entities { node_id: entityId,
title: entityLabel } } }
Queries: Alias •This allows you to rename stuff query {
nodeQuery { entities { node_id: entityId, title: entityLabel } } }
Queries: Alias •This allows you to rename stuff query {
nodeQuery { entities { node_id: entityId, title: entityLabel } } }
Queries: Alias •This allows you to rename stuff query {
nodeQuery { entities { node_id: entityId, title: entityLabel } } }
Queries: Alias •This allows you to rename stuff query {
nodeQuery { entities { node_id: entityId, title: entityLabel } } }
Queries: Alias •This allows you to rename stuff query {
nodeQuery { entities { node_id: entityId, title: entityLabel } } }
Queries: Alias { "node_id": "26", "title": "The Herreshoff Manufacturing Company
is officially established." },
Queries: Alias { "entityId": "26", "entityLabel": "The Herreshoff Manufacturing Company
is officially established." },
Queries: Alias query { ship1: nodeById(id: "5") { entityLabel }
ship2:nodeById(id: "7") { entityLabel } }
Queries: Alias •Also allows you to run query twice query
{ ship1: nodeById(id: "5") { entityLabel } ship2:nodeById(id: "7") { entityLabel } }
Queries: Alias { "data": { "ship1": { "entityLabel": "Reliance -
America's Cup Defender" }, "ship2": { "entityLabel": "Alera - An Icon of One-Design Racing" } } }
Queries: Fragments query { nodeQuery { entities { entityId entityType
entityLabel } } }
Queries: Fragments query { nodeQuery { entities { ...nodeBasics }
} } fragment nodeBasics on Node { entityId entityType entityLabel }
Queries: Fragments
Queries: Fragments •DRYing out your queries using fragments can make
it easier to keep track of common structures
Queries: Arguments query getShip($nid: String = "") { nodeById(id:$nid) {
entityId entityType entityLabel } } {"nid": "5"}
Queries: Arguments •query query getShip($nid: String = "") { nodeById(id:$nid)
{ entityId entityType entityLabel } } {"nid": "5"}
Queries: Arguments •query query getShip($nid: String = "") { nodeById(id:$nid)
{ entityId entityType entityLabel } } {"nid": "5"}
Queries: Arguments •query query getShip($nid: String = "") { nodeById(id:$nid)
{ entityId entityType entityLabel } } •variable {"nid": "5"}
Queries: Arguments { "data": { "nodeById": { "entityId": "5", "entityType":
"node", "entityLabel": "Reliance - America's Cup Defender" } } }
Queries: Referenced Entities query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]})
{ entities { ...on NodeVessel { entityId, entityLabel, slides: fieldVSlides { slide: entity { fieldDisplayTitle { value } } } } } } }
Queries: Referenced Entities query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]})
{ entities { ...on NodeVessel { entityId, entityLabel, slides: fieldVSlides { slide: entity { fieldDisplayTitle { value } } } } } } }
Queries: Referenced Entities query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]})
{ entities { ...on NodeVessel { entityId, entityLabel, slides: fieldVSlides { slide: entity { fieldDisplayTitle { value } } } } } } }
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" } } },
Queries: Filters query { nodeQuery(filter:{conditions: [{field: "type", value: "vessel"}]}) {
entities { entityId, entityLabel } } }
Queries: Filters •Usually node type query { nodeQuery(filter:{conditions: [{field: "type",
value: "vessel"}]}) { entities { entityId, entityLabel } } }
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" },
Slightly more advanced
Custom Types: Adding Fields to Types { nodeQuery(filter: {conditions: [{field:
"type", value: "vessel", operator: EQUAL}]}) { entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url } } } } } } }
Custom Types: Adding Fields to Types { nodeQuery(filter: {conditions: [{field:
"type", value: "vessel", operator: EQUAL}]}) { entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url } } } } } } }
Custom Types: Adding Fields to Types { nodeQuery(filter: {conditions: [{field:
"type", value: "vessel", operator: EQUAL}]}) { entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url } } } } } } }
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" } } } },
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; } } }
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" * )
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" * )
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" * )
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; } }
{ nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url, relative_path } } } } } } }
{ nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url, relative_path } } } } } } }
{ nodeQuery(filter: {conditions: [{field: "type", value: "vessel", operator: EQUAL}]}) {
entities { ... on NodeVessel { entityId, title, fieldTimelineThumbnail { entity { fieldMediaImage { url, relative_path } } } } } } }
What about documentation?
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
Custom Types: Settings Form
Custom Types: Settings Form •A node is in the core
entity types. But what about stuff that isn’t?
Custom Types: Settings Form •A node is in the core
entity types. But what about stuff that isn’t? •Settings example
Custom Types: Settings Form
Custom Types: Settings Form •Basic custom form for saving to
Drupal::state()
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); } }
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" * }, * ) */
Custom Types: Settings Form query{ Payload(settings_name:"thf_utility.gcib_settings") }
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" }" ] } }
Case Study
Case Study •Two products
Case Study •Two products •One Cinder touchscreen
Case Study •Two products •One Cinder touchscreen •One JS touchscreen
None
What did we learn?
What did we learn? •Empty structures/values break Cinder
What did we learn? •Empty structures/values break Cinder •Making queries
consistent is a challenge
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
DEMO TIME
None
None
We’re hiring.
None
THANK YOU. Mark Llobrera Technology Director, Bluecadet Philadelphia https://speakerdeck.com/mllobrera @dirtystylus
@bluecadet