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
63
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
320
Drupaldelphia 2018: Drupal Unhitched: The CMS in Decoupled Architectures
mllobrera
0
260
Drupalcon 2017: Pattern Language: Pattern Libraries in the Wild
mllobrera
0
460
DIW 2016: Github Project Scaffolding with Google Sheets and Zenhub
mllobrera
0
240
Philly Tech Week 2016: Decoupled Development: Feeding Your Application with Off-the-Shelf Tools
mllobrera
0
150
Drupal Beyond the Browser: Using Drupal to Power Apps and Touchscreens
mllobrera
0
360
Decoupled Development with WordPress JSON APIs
mllobrera
0
710
Drupaldelphia 2014: Progressive Enhancement in Drupal 7, Using Ajax-Include Patterns
mllobrera
0
170
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
私の後悔をAWS DMSで解決した話
hiramax
4
140
Flutter로 Gemini와 MCP를 활용한 Agentic App 만들기 - 박제창 2025 I/O Extended Seoul
itsmedreamwalker
0
150
コンテキストエンジニアリング Cursor編
kinopeee
1
710
Langfuseと歩む生成AI活用推進
licux
3
300
物語を動かす行動"量" #エンジニアニメ
konifar
14
5.5k
SOCI Index Manifest v2が出たので調べてみた / Introduction to SOCI Index Manifest v2
tkikuc
1
110
モバイルアプリからWebへの横展開を加速した話_Claude_Code_実践術.pdf
kazuyasakamoto
0
270
tool ディレクティブを導入してみた感想
sgash708
1
150
TROCCO×dbtで実現する人にもAIにもやさしいデータ基盤
nealle
0
330
実践!App Intents対応
yuukiw00w
1
350
Honoアップデート 2025年夏
yusukebe
1
860
書き捨てではなく継続開発可能なコードをAIコーディングエージェントで書くために意識していること
shuyakinjo
1
310
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
Mobile First: as difficult as doing things right
swwweet
223
9.9k
Code Reviewing Like a Champion
maltzj
525
40k
How to Think Like a Performance Engineer
csswizardry
25
1.8k
Optimizing for Happiness
mojombo
379
70k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.6k
Side Projects
sachag
455
43k
Agile that works and the tools we love
rasmusluckow
329
21k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
139
34k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
820
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