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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
dirtystylus
May 10, 2019
Programming
76
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Rapid Data APIs using GraphQL and Drupal
A quick introduction to using GraphQL with Drupal 8.
dirtystylus
May 10, 2019
More Decks by dirtystylus
See All by dirtystylus
Decoupled Drupal Days 2018: Drupal Unhitched
mllobrera
0
380
Drupaldelphia 2018: Drupal Unhitched: The CMS in Decoupled Architectures
mllobrera
0
320
Drupalcon 2017: Pattern Language: Pattern Libraries in the Wild
mllobrera
0
520
DIW 2016: Github Project Scaffolding with Google Sheets and Zenhub
mllobrera
0
280
Philly Tech Week 2016: Decoupled Development: Feeding Your Application with Off-the-Shelf Tools
mllobrera
0
200
Drupal Beyond the Browser: Using Drupal to Power Apps and Touchscreens
mllobrera
0
400
Decoupled Development with WordPress JSON APIs
mllobrera
0
840
Drupaldelphia 2014: Progressive Enhancement in Drupal 7, Using Ajax-Include Patterns
mllobrera
0
210
Drupaldelphia 2013 - Tyler School of Art: A Case Study in User-Centered Decision-Making
mllobrera
0
1.1k
Other Decks in Programming
See All in Programming
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
140
OS アップデート対応の取り組み方がもっと共有されてほしい
andpad
0
110
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
AI 輔助遺留系統現代化的經驗分享
jame2408
1
1.2k
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
220
AIエージェントで 変わるAndroid開発環境
takahirom
1
450
ローカルLLMでどこまでコードが書けるか -縮小版 / How much code can be written on a local LLM Shortened
kishida
2
180
なぜ関数型プログラミングで「型」と「証明」が語られるのか #fp_matsuri
kajitack
3
590
Creating Composable Callables in Contemporary C++
rollbear
0
190
スマートグラスで並列バイブコーディング
hyshu
0
280
dRuby over BLE
makicamel
2
410
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
790
Featured
See All Featured
Color Theory Basics | Prateek | Gurzu
gurzu
0
380
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
300
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
460
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Abbi's Birthday
coloredviolet
3
8.5k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
210
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Chasing Engaging Ingredients in Design
codingconduct
0
230
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