WiFi
DFMguest | DFMvisitor
WORKSHOP TOOLS
• Local WordPress Install
• WPGraphQL plugin, activated, permalinks flushed
• WPGraphiQL plugin activated (or GraphiQL Desktop App)
• Code / Text Editor
Content Syndication
With the WP REST API and WPGraphQL
Content Syndication
With the WP REST API and WPGraphQL
WordCamp for Publishers 2017
Jason Bahl | @jasonbahl
Who Am I?
- Senior WordPress Engineer at Digital First Media
- Denver Native
- WordPress Developer for 9+ years
- Creator / Maintainer of WPGraphQL
Workshop Overview
- How Digital First Media uses the WP REST API to syndicate content at scale, and lessons learned
- Intro to GraphQL
- Hands on with WPGraphQL
Content Syndication
Powered by the WP REST API
- Push Syndication
- Pull Syndication
- Distributed Client Syndication
Push Syndication
-
Content originates in one system and is pushed to other systems
- Our "Hub" validates the data and checks the syndication rules, and sends the data to it's final destination
Push Syndication
Pull Syndication
-
Content originates in an external system and is "pulled" into the user's system.
- Users browse remote articles
- click "Import"
- article is fetched from the external system
- article is imported into the user's system
Pull Syndication
{
"id": 123,
"date": "2017-08-09T13:39:18",
"date_gmt": "2017-08-09T18:39:18",
"guid": {
"rendered": "http://www.denverpost.com/?p=123"
},
"modified": "2017-08-10T17:16:57",
"modified_gmt": "2017-08-10T22:16:57",
"slug": "article-title",
"status": "publish",
"type": "post",
"link": "https://poststatus.com/article-title/",
"title": {
"rendered": "Article Title"
},
"content": {
"rendered": "Some content",
},
...
}
By the Numbers
Push / Pull Syndication at DFM
540,096
Articles syndicated across 9 WordPress sites since
Feb 2016 - May 2017
*not including article updates, media or other connected entities
~1,280
Articles syndicated each day
Distributed Client Syndication
-
Content is fetched from remote system and displayed by the client
-
The content is only represented by the client system, not duplicated in another data store
Distributed Client Syndication
{
"id": 123,
"date": "2017-08-09T13:39:18",
"date_gmt": "2017-08-09T18:39:18",
"guid": {
"rendered": "http://www.denverpost.com/?p=123"
},
"modified": "2017-08-10T17:16:57",
"modified_gmt": "2017-08-10T22:16:57",
"slug": "article-title",
"status": "publish",
"type": "post",
"link": "https://poststatus.com/article-title/",
"title": {
"rendered": "Article Title"
},
"content": {
"rendered": "Some content",
},
...
}
What We've Learned
About Syndicating Content with REST
- The WP REST API is easy to work with
- Easy to get started
- Simply make a request to an endpoint!
- Easy to add endpoints
- Easy to add fields
What We've Learned
About Syndicating Content with REST
- Designing a REST API is hard
- Constantly over fetching and under fetching
- Lots and lots of HTTP requests
- Tight client-server coupling
- Implicit Requests Make Maintenance Difficult
- Maintaining Documentation
- Versioning is difficult
Designing a REST API is hard
5,207 bytes
4,725 bytes
5,823 bytes
Over / Under Fetching
{
"data": {
"articles": [
{
"author": {
"avatar": {
"url": "http://gravatar.com/?id=123&w=50"
},
"name": "Hillary",
"link": "http://hillarys-site.com",
"followLink": "http://example.com/?follow_author=1"
},
"title": "The Bookshop, by Penelope Fitzgerald",
"link": "http://example.com/article-path",
"excerpt": "I am very much enjoying...",
"publication": {
"name": "Vulpes Libris",
"link": "http://example-publication.com"
}
}
]
}
}
GET /posts
{
"id": 38424,
"date": "2017-08-09T13:39:18",
"date_gmt": "2017-08-09T18:39:18",
"guid": {
"rendered": "https://example.com/?p=38424"
},
"modified": "2017-08-10T17:16:57",
"modified_gmt": "2017-08-10T22:16:57",
"slug": "publish-conference-pictures",
"status": "publish",
"type": "post",
"link": "https://example.com/publish-conference-pictures/",
"title": {
"rendered": "The Bookshop, by Penelope Fitzgerald"
},
"content": {
"rendered": "<p>I am very much enjoying...Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n<p>Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.</p>\n<p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p>\n<p>
Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail. Keeping your eye on the ball while performing a deep dive on the start-up mentality to derive convergence on cross-platform integration.</p>\n<p>Collaboratively administrate empowered markets via plug-and-play networks. Dynamically procrastinate B2C users after installed base benefits. Dramatically visualize customer directed convergence without revolutionary ROI.</p>\n<p>Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar solutions without functional solutions.</p>\n<p>Completely synergize resource taxing relationships via premier niche markets. Professionally cultivate one-to-one customer service with robust ideas. Dynamically innovate resource-leveling customer service for state of the art customer service.</p>\n<p>Objectively innovate empowered manufactured products whereas parallel platforms. Holisticly predominate extensible testing procedures for reliable supply chains. Dramatically engage top-line web services vis-a-vis cutting-edge deliverables.</p><p>Proactively envisioned multimedia based expertise and cross-media growth strategies. Seamlessly visualize quality intellectual capital without superior collaboration and idea-sharing. Holistically pontificate installed base portals after maintainable products.</p><p>Phosfluorescently engage worldwide methodologies with web-enabled technology. Interactively coordinate proactive e-commerce via process-centric "outside the box" thinking. Completely pursue scalable customer service through sustainable potentialities.</p>",
"protected": false
},
"excerpt": {
"rendered": "<p>I am very much enjoying...Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n",
"protected": false
},
"author": 1,
"featured_media": 38425,
"comment_status": "open",
"ping_status": "closed",
"sticky": false,
"template": "",
"format": "standard",
"meta": [
],
"categories": [
6,
196
],
"tags": [
178
],
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/posts/38424"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/posts"
}
],
"about": [
{
"href": "https://example.com/wp-json/wp/v2/types/post"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/1"
}
],
"replies": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/comments?post=38424"
}
],
"version-history": [
{
"href": "https://example.com/wp-json/wp/v2/posts/38424/revisions"
}
],
"wp:featuredmedia": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/media/38425"
}
],
"wp:attachment": [
{
"href": "https://example.com/wp-json/wp/v2/media?parent=38424"
}
],
"wp:term": [
{
"taxonomy": "category",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/categories?post=38424"
},
{
"taxonomy": "post_tag",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/tags?post=38424"
},
{
"taxonomy": "yst_prominent_words",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/yst_prominent_words?post=38424"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
GET /posts foreach post: GET /post/:id/:author GET /author/:id/avatar GET /post/:id/publication
Over / Under Fetching
- Individual REST payloads very rarely include exactly what you need
- Extra, unnecessary data passing over the network
- Can lead to performance issues, especially on mobile networks
Common ways to fight Over / Under Fetching
- Make more HTTP requests to get the resources you need?
- Waaaay more data than is needed (performance issues)
- Build feature-specific endpoints
- Creates tight client / server coupling (hard to maintain/version)
- WP REST API Embeds?
- Waaaay more data than is needed (performance issues)
- No way to specify what you want from the embedded objects
- Ad-hoc filters via query params ?
- https://wordpress.org/plugins/rest-api-filter-fields
- /posts/?fields=authors,site
- Non-standard, hard to debug and maintain, nested fields?
Maintaining Implicit Requests Over Time
- GET /posts
- GET /authors
- GET /sites
- GET /publications
- GET /tags
- GET /categories
Implicit Requests / Versioning / Documentation
- Consuming code isn't explicit
- No idea what is coming back in the payload, so you need to look at the REST documentation again and again
- Server doesn't know what fields are being used
- Hard to deprecate fields
- leads to versioned endpoints
- leads to duplicate code / technical debt
- leads to undocumented endpoints
- GET /posts
- GET /posts/:id
- GET /authors
- GET /authors/:id
- Constantly over fetching and under fetching
- Lots and lots of HTTP requests
- Tight client-server coupling
- Implicit Requests Make Maintenance Difficult
- Maintaining Documentation
- Versioning is difficult
- Fetch exactly what you need, nothing more, nothing less
- Multiple resources in a single HTTP request
- Decoupled client-server relationship
- Explicit requests ease maintenance
- Self Documenting Schema / Type System
- Easy, pain free versioning
GraphQL
A Query Language for your API
GraphQL is a query language for APIs and a runtime for fulfilling those
queries with your existing data. GraphQL provides a complete
and understandable description of the data in your API, gives
clients the power to ask for exactly what they need
and nothing more, makes it easier to evolve APIs over time,
and enables powerful developer tools
GraphQL
Graph != Graph Databases
Graph != Graph Search
Graph = Application Data Graph
QL = Query Language
GraphQL: A Query Language for Your Application Data Graph
Post
Category
Category
Category
Post
title
"Hello World"
title
"GoodBye Mars"
Image
Image
Image
name
"news"
name
"crime"
name
"sports"
Image
GraphQL
GraphQL lets us pick trees out of the Application Data Graph
GraphQL
query {
post(id: "...") {
title
link
coauthors {
name
}
}
}
{
data: {
post: {
title: "Hello World!"
link: "http://site.com/hello-world"
coauthors: [
{
name: "John Doe",
},
{
name: "Jane Smith",
}
]
}
}
}
GraphQL
Post
CoAuthor
CoAuthor
CoAuthor
Post
Featured
Image
title
"Hello World"
title
"GoodBye Mars"
Avatar
Avatar
Avatar
name
"jane doe"
name
"sue smith"
name
"john doe"
GraphQL
GET /post/:id
{
"id": 38424,
"date": "2017-08-09T13:39:18",
"date_gmt": "2017-08-09T18:39:18",
"guid": {
"rendered": "https://example.com/?p=38424"
},
"modified": "2017-08-10T17:16:57",
"modified_gmt": "2017-08-10T22:16:57",
"slug": "publish-conference-pictures",
"status": "publish",
"type": "post",
"link": "https://example.com/publish-conference-pictures/",
"title": {
"rendered": "The Bookshop, by Penelope Fitzgerald"
},
"content": {
"rendered": "<p>I am very much enjoying...Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n<p>Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.</p>\n<p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p>\n<p>
Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail. Keeping your eye on the ball while performing a deep dive on the start-up mentality to derive convergence on cross-platform integration.</p>\n<p>Collaboratively administrate empowered markets via plug-and-play networks. Dynamically procrastinate B2C users after installed base benefits. Dramatically visualize customer directed convergence without revolutionary ROI.</p>\n<p>Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar solutions without functional solutions.</p>\n<p>Completely synergize resource taxing relationships via premier niche markets. Professionally cultivate one-to-one customer service with robust ideas. Dynamically innovate resource-leveling customer service for state of the art customer service.</p>\n<p>Objectively innovate empowered manufactured products whereas parallel platforms. Holisticly predominate extensible testing procedures for reliable supply chains. Dramatically engage top-line web services vis-a-vis cutting-edge deliverables.</p><p>Proactively envisioned multimedia based expertise and cross-media growth strategies. Seamlessly visualize quality intellectual capital without superior collaboration and idea-sharing. Holistically pontificate installed base portals after maintainable products.</p><p>Phosfluorescently engage worldwide methodologies with web-enabled technology. Interactively coordinate proactive e-commerce via process-centric "outside the box" thinking. Completely pursue scalable customer service through sustainable potentialities.</p>",
"protected": false
},
"excerpt": {
"rendered": "<p>I am very much enjoying...Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n",
"protected": false
},
"author": 1,
"featured_media": 38425,
"comment_status": "open",
"ping_status": "closed",
"sticky": false,
"template": "",
"format": "standard",
"meta": [
],
"categories": [
6,
196
],
"tags": [
178
],
"_links": {
"self": [
{
"href": "https://example.com/wp-json/wp/v2/posts/38424"
}
],
"collection": [
{
"href": "https://example.com/wp-json/wp/v2/posts"
}
],
"about": [
{
"href": "https://example.com/wp-json/wp/v2/types/post"
}
],
"author": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/users/1"
}
],
"replies": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/comments?post=38424"
}
],
"version-history": [
{
"href": "https://example.com/wp-json/wp/v2/posts/38424/revisions"
}
],
"wp:featuredmedia": [
{
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/media/38425"
}
],
"wp:attachment": [
{
"href": "https://example.com/wp-json/wp/v2/media?parent=38424"
}
],
"wp:term": [
{
"taxonomy": "category",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/categories?post=38424"
},
{
"taxonomy": "post_tag",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/tags?post=38424"
},
{
"taxonomy": "yst_prominent_words",
"embeddable": true,
"href": "https://example.com/wp-json/wp/v2/yst_prominent_words?post=38424"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
GET /users/1
{
"id": 1,
"name": "Jane Doe",
"url": "https://example.com",
"description": "I'm a WordPress developer from Birmingham, Alabama. I'm the creator and editor of Post Status.",
"link": "https://poststatus.com/profiles/brian-krogsgard/",
"slug": "jane-doe",
"avatar_urls": {
"24": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=96&d=mm&r=g"
},
"meta": [
],
"_links": {
"self": [
{
"href": "https://poststatus.com/wp-json/wp/v2/users/1"
}
],
"collection": [
{
"href": "https://poststatus.com/wp-json/wp/v2/users"
}
]
}
}
GET /users/2
{
"id": 2,
"name": "John Doe",
"url": "https://example.com",
"description": "I'm a WordPress developer from Birmingham, Alabama. I'm the creator and editor of Post Status.",
"link": "https://poststatus.com/profiles/brian-krogsgard/",
"slug": "john-doe",
"avatar_urls": {
"24": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=24&d=mm&r=g",
"48": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=48&d=mm&r=g",
"96": "https://secure.gravatar.com/avatar/6b89515a9781282ae3a66d4b6173523c?s=96&d=mm&r=g"
},
"meta": [
],
"_links": {
"self": [
{
"href": "https://poststatus.com/wp-json/wp/v2/users/1"
}
],
"collection": [
{
"href": "https://poststatus.com/wp-json/wp/v2/users"
}
]
}
}
5,460 bytes
819 bytes
819 bytes
(sample payloads from poststatus.com)
GraphQL
query {
post(id: "...") {
title
link
coauthors {
name
}
}
}
{
data: {
post: {
title: "Hello World!"
link: "http://site.com/hello-world"
coauthors: [
{
name: "John Doe",
},
{
name: "Jane Smith",
}
]
}
}
}
231 bytes
GraphQL
REST
- 3 network requests
- Bytes Downloaded: 7,240
- Lots of consumer transformation needed
GraphQL
- 1 network request
- Bytes Downloaded: 231
- No consumer transformation needed
query {
posts {
edges {
node {
}
}
}
}
author {
name
avatar(size: 50) {
url
}
}
followLink
title
featuredImage(width: 300) {
url
}
excerpt
site {
name
link
}
GraphQL
WPGraphQL
A Query Language for your WordPress API
GraphQL
A Query Language for your API
GraphQL is a query language for APIs and a runtime for fulfilling those
queries with your existing data. GraphQL provides a complete
and understandable description of the data in your API, gives
clients the power to ask for exactly what they need
and nothing more, makes it easier to evolve APIs over time,
and enables powerful developer tools
WPGraphQL is an extendable open-source WordPress plugin that brings the power of GraphQL to your WordPress install. WPGraphQL defines GraphQL Types for core WordPress entities, such as posts, terms and users, and defines core entry points into the WordPress Application Data Graph via GraphQL Queries and Mutations. WPGraphQL gives clients the power to ask for exactly what they need from WordPress and enables powerful developer tools.
- Download the plugin from Github
- Add to your WordPress install
- /wp-content/plugins
- Activate the plugin
- Flush the permalinks
Using the Plugin
Tools to Run Queries
WPGraphiQL
https://github.com/wp-graphql/wp-graphiql
Download, install & activate plugin, navigate to GraphiQL in the WordPress Dashboard
-
explore GraphQL Documentation
-
query a list of posts
-
query nested resources
-
query multiple root resources
-
using the node query
-
using query fragments with GraphQL
-
using aliases with GraphQL
-
use variables with GraphQL
-
queries with variables and pagination
-
mutation - createPost
Exploring WPGraphQL
GraphQL Docs
IDE Support
GraphQL Tooling
Let's build a plugin!
Extending WPGraphQL
<?php
/**
* Plugin Name: WPGraphQL WordCamp Publishers Extension
* Description: Plugin that extends WPGraphQL
*/
- Create a new directory
- /plugins/wpgraphql-wordcamp-publishers
- Create a new plugin file
- wpgraphql-wordcamp-publishers.php
Add a Root Entry Point
Extending WPGraphQL
Add a Root Entry Point
Extending WPGraphQL
add_action( 'graphql_root_queries', function( $fields ) {
$fields['wordCampRocks'] = [
'type' => \WPGraphQL\Types::string(),
'description' => __( 'An example field showing how to add
to the root schema', 'wp-graphql-publishers' ),
'resolve' => function() {
return 'Yes, it does';
},
];
return $fields;
} );
Add a Field to the Post Schema
Extending WPGraphQL
Add a Field to the Post Schema
Extending WPGraphQL
add_action( 'graphql_post_fields', function( $fields ) {
$fields['color'] = [
'type' => \WPGraphQL\Types::string(),
'description' => __( 'An example showing how to add a
field to the "post" schema', 'wp-graphql-publishers' ),
'resolve' => function( \WP_Post $post ) {
return get_post_meta( $post->ID, 'color', true );
},
];
return $fields;
}, 10, 1);
Add Custom Post Type to the Schema
Extending WPGraphQL
Add Custom Post Type to the Schema
Extending WPGraphQL
add_action( 'init', function() {
register_post_type( 'book', [
'label' => __( 'Books', 'wp-graphql-publishers' ),
'supports' => [ 'title', 'editor', 'custom-fields' ],
'public' => true,
'show_in_graphql' => true,
'graphql_single_name' => 'book',
'graphql_plural_name' => 'books',
] );
} );
Add Custom Taxonomy to the Schema
Extending WPGraphQL
Add Custom Taxonomy to the Schema
Extending WPGraphQL
add_action( 'init', function() {
register_taxonomy( 'genre', 'book', [
'label' => __( 'Genre' ),
'public' => true,
'show_in_graphql' => true,
'graphql_single_name' => 'genre',
'graphql_plural_name' => 'genres',
'hierarchical' => true,
]);
} );
Add a Custom Field with Mutation Support
Extending WPGraphQL
Add a Custom Field with Mutation Support
Extending WPGraphQL
add_action( 'graphql_book_fields', function( $fields ) {
$fields['price'] = [
'type' => \WPGraphQL\Types::string(),
'description' => __( 'The price of the book', 'wp-graphql-publishers' ),
'resolve' => function( \WP_Post $book ) {
$price = get_post_meta( $book->ID, 'price', true );
return ! empty( $price ) ? $price : null;
},
];
return $fields;
}, 10, 1);
1. Add the field to the type
Add a Custom Field with Mutation Support
Extending WPGraphQL
add_action( 'graphql_post_object_mutation_input_fields', function( $fields, \WP_Post_Type $post_type_object ) {
if ( 'book' === $post_type_object->name ) {
$fields['price'] = [
'type' => \WPGraphQL\Types::string(),
'description' => __( 'The price of the book', 'wp-graphql-publishers' ),
];
}
return $fields;
}, 10, 2 );
2. Add the input field to the mutation input
Add a Custom Field with Mutation Support
Extending WPGraphQL
add_action( 'graphql_post_object_mutation_update_additional_data', 'prefix_update_price', 10, 3 );
function prefix_update_price( $post_id, $input, \WP_Post_Type $post_type_object ) {
if ( 'book' === $post_type_object->name && ! empty( $input['price'] ) ) {
update_post_meta( $post_id, 'price', $input['price'] );
}
}
3. Add the input field to the mutation input
Add a Custom Field with Mutation Support
Extending WPGraphQL
add_action( 'graphql_post_object_mutation_input_fields', function( $fields, \WP_Post_Type $post_type_object ) {
if ( 'book' === $post_type_object->name ) {
$fields['price'] = [
'type' => \WPGraphQL\Types::string(),
'description' => __( 'The price of the book', 'wp-graphql-publishers' ),
];
}
return $fields;
}, 10, 2 );
2. Add the input type
Content Syndication
By Jason Bahl
Content Syndication
- 4,841