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
- Preprocess hook for node: template_preprocess_node
// 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...
-
Dig further (from Philipp Melab - the creator of the module + co-maintainer of the graphql module)
- Soft decoupling with GraphQL + Twig: https://www.youtube.com/watch?v=QsmeXMZ0foM
- Atomic design with GraphQL + Twig: https://www.youtube.com/watch?v=9AJNp72-3D8
- Drupal Europe presentation: https://www.youtube.com/watch?v=BFf3stFHmk4
- Resources
Questions?
GraphQL and twig: decouple Drupal's front end // DrupalCamp Spain 2019
By Fran García-Linares
GraphQL and twig: decouple Drupal's front end // DrupalCamp Spain 2019
- 2,186