Share
Embed
Rapid Data APIs using GraphQL and Drupal
A quick introduction to using GraphQL with Drupal 8.
dirtystylus
May 10, 2019
Transcript
107 slides
-
What is GraphQL? •https://graphql.org •“GraphQL is a query language for
your API.” •Facebook circa 2012, open-sourced circa 2015 -
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) •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 -
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 -
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}}} -
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 •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" }, -
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 } } } } } } } -
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 •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 { "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" }" ] } } -
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