GraphQL and Twig: decouple Drupal's front end

Fran García-Linares

About me

fran.garcia@amazeelabs.com

Fran García-Linares, Drupal web developer

fjgarlin@gmail.com

https://www.drupal.org/u/fjgarlin

...linkedin, github, slides.com, strava  fjgarlin

We are

HIRING

Agenda (40min)

Intro (5 min)

GraphQL (5 min)

GraphQL and Twig (20 min)

Steps to decoupling from here (5 min)

Q & A (5 min)

GraphQL 

GraphQL?

  • Query language for your data
  • Ask for what you need, get just that
  • Many resources in a single request

 

 

GraphQL

is not

SQL

GraphQL

GraphQL vs SQL

  • What's under a GraphQL endpoint?
  • Full control of what's exposed

It usually appears next to

- JSON-API

- Decoupled build

- Apollo

- React, VueJS...

- Twig??

GraphQL and Twig

composer require drupal/graphql_twig
[drupal8-composer]$ composer require drupal/graphql_twig

Using version ^1.0@beta for drupal/graphql_twig
./composer.json has been updated
> DrupalProject\composer\ScriptHandler::checkComposerVersion
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
  - Installing webonyx/graphql-php (v0.12.6): Loading from cache
  - Installing drupal/graphql (3.0.0-rc3): Loading from cache
  - Installing drupal/graphql_twig (1.0.0-beta11): Loading from cache
Writing lock file
Generating autoload files
> DrupalProject\composer\ScriptHandler::createRequiredFiles

GraphQL and Twig

drush -y en graphql_twig graphql_core 
# development.services.yml

parameters:
  twig.config:
    debug: true

GraphQL and Twig

First template: node.html.twig

 

{#graphql
query($node: String!) {
  node:nodeById(id: $node) {
    title:entityLabel
  }
}
#}
<h5>
  {{ "Title brought via GraphQL" | t }}: 
  <em>{{ graphql.node.title }}</em>
</h5>
<hr>

{# rest of file... #}

on hover...

on click...

GraphQL and Twig

First template: node.html.twig

 

GraphQL and Twig

Extending markup (extra variables)

Bring additional variables not available in the template.

# node.html.twig.gql

query($node: String!) {
  node:nodeById(id: $node) {
    title:entityLabel
  }
  nodes:nodeQuery {
    count
  }
}
{# node.html.twig #}

<h5>
  {{ "Title brought via GraphQL" | t}}: 
  <em>{{ graphql.node.title }}</em>
</h5>
<h6>
  {{ "Pages on the site" | t}}:
  {{ graphql.nodes.count }}
</h6>
<hr>

{# rest of file... #}

GraphQL and Twig

Replacing markup

  • Replacing full template
  • Ignoring Drupal's available variables
  • Get all needed data for that template via GraphQL
  • For "smaller" templates (fields, nodes, blocks)
  • Full control of data and markup
  • Very close to a fully decoupled app
  • Could still use advantages of Drupal architecture
  • Progressive decoupling

GraphQL and Twig

Preprocessing no more

 

Problem: Bring total number of nodes on site and show on every node page

Classic Drupal Solutions:

  • Site builder:
    • View block with aggregated query
    • Block management and set block to visible under 'node/*" pages.
    • Position of block can only be above or below 'Main content'
  • ​​​Back-end + front-end:
    • Preprocess hook for node: template_preprocess_node
      • ​Then run query* to get number of nodes and pass result as variable to the template
      • Then override template to show the value
// Get node count for site.
public function nodeCountState() {
  $query = \Drupal::entityQuery('node')->condition('status', \Drupal\node\NodeInterface::PUBLISHED);
  $result = $query->count()->execute();
  return $result;
}

GraphQL and Twig

Preprocessing no more

 

Problem: Bring total number of nodes on site and show on every node page

{#graphql
query() {
  nodes:nodeQuery {
    count
  }
}
#}
...
{{ "Count" | t }}: {{ graphql.nodes.count }}
...

GraphQL + twig solution:

  • node.html.twig (or separate file)

GraphQL and Twig

Preprocessing no more

 

Problem: ... and languages available

{#graphql
query() {
  languages:availableLanguages {
    name
  }
}
#}
...
<ul>
{% for language in graphql.languages %}
  <li>{{ language.name }}</li>
{% endfor %}
</ul>
...

GraphQL + twig solution:

  • node.html.twig (or separate file)

GraphQL and Twig

Preprocessing no more

 

Problem: ... and all the previous and a count and list of users...

* It uses Drupal permissions so the data is curated

query($node: String!) {
  node:nodeById(id: $node) {
    title:entityLabel
  }
  nodes:nodeQuery {
    count
  }
  languages:availableLanguages {
    id
    name
  }
  users:userQuery {
    count
    list:entities {
      name:entityLabel
    }
  }
}

GraphQL + twig solution:

  • node.html.twig.gql

and...

GraphQL and Twig

Performance

  • Everything done in the same request, so no additional HTTP request is made
  • Queries are cached
  • Queries are grouped within multiple templates
  • Under the hood, it's like an additional hook
  • Server side rendering ;-)

 

Advantages

  • We still get Drupal forms
  • We still get translations out of the box
  • Works alongside with standard Drupal
  • Reduced risk

GraphQL and Twig

Recap soft decoupling

  • Augment template available variables
  • No preprocessing needed
  • Query in same file or separate (gql) file
  • Request any kind of data in any template
  • No additional trips to server to request data
  • No need to decouple... but:
    • ​Easy to partly decouple (ie: some React driven components)
    • Lays a foundation path to fully decouple (​see next slides)

Steps to decouple (if you want)

1. Take .twig file

2. Change twig logic for React / Vue logic

import React from "react";
import { render } from "react-dom";
import { ApolloClient, gql, ApolloProvider, Query } from "react-apollo";

const client = new ApolloClient({ uri: "https://drupal.url/graphql" });
const graphql_query = gql`
  query {
    user:userById(id: "1") {
      label:entityLabel
    }
  }
`;

const App = () => (
  <ApolloProvider client={client}>
    <Query query={graphql_query}>
      {({ loading, error, data }) => {
        if (loading) return <div>Loading...</div>;
        if (error) return <div>Error </div>;
        return <div>{data.user.label}</div>;
      }}
    </Query>
  </ApolloProvider>
);

render(App, document.getElementById("root"));
{#graphql
query($user: String!) {
  user:userById(id: $user) {
    label:entityLabel
  }
}
#}
<div>
  {{ graphql.data.user.label }}
</div>

removing app code >>>

Steps to decouple (if you want)

1. Take .twig file

2. Change twig logic for React / Vue logic

{#graphql
query($user: String!) {
  user:userById(id: $user) {
    label:entityLabel
  }
}
#}
<div>
  {{ graphql.data.user.label }}
</div>
// imports, graphql endpoint...

const graphql_query = gql`
  query {
    user:userById(id: "1") {
      label:entityLabel
    }
  }
`;

// app stuff...
    <Query query={graphql_query}>
      {({ loading, error, data }) => {
        return <div>{data.user.label}</div>;
      }}
    </Query>
// app stuff...

 

Questions?

Made with Slides.com