Using DRY code to render blocks for server (PHP) and client (JS) sides

Dynamic (Gutenberg) blocks are blocks that build their structure and content on the fly when the block is rendered on the front end.

Rendering a dynamic block in the front-end (to display it in the WordPress editor) and in the server-side (to generate the HTML for the blog post) will typically fetch its data in two different ways:

  • Connecting to the API on the client-side (JavaScript)
  • Calling WordPress functions on the server-side (PHP)

With Gato GraphQL and extensions, it is possible to make this logic DRY, having a single source of truth to fetch data for both the client and server-sides. Let's explore how to do this.

Storing GraphQL queries in .gql files permalink

In order to connect to the GraphQL server from the client, we typically execute the GraphQL query embedded within the JavaScript code, like this:

const response = await fetch(endpoint, {
body: JSON.stringify({
query: `
query {
posts {
id
title
author {
id
name
}
}
}
`

)
} );

We can alternatively store the GraphQL query in a .gql (or .graphql) file, and import its contents using Webpack's raw-loader:

// File webpack.config.js
const config = require( '@wordpress/scripts/config/webpack.config' );

config.module.rules.push(
{
test: /\.(gql|graphql)$/i,
use: 'raw-loader',
},
);

module.exports = config;

(This code works for Webpack v4; for v5, we must use Asset Modules instead.)

Next, we place the GraphQL query inside a .gql file:

# File graphql-documents/fetch-posts-with-author.gql
query {
posts {
id
title
author {
id
name
}
}
}

Finally, within the block's code, we import the file and pass its contents to fetch:

import graphQLQuery from './graphql-documents/fetch-posts-with-author.gql';

// ...

const response = await fetch(endpoint, {
body: JSON.stringify({
query: graphQLQuery
)
} );

Resolving .gql files in the server-side permalink

The GraphQL file we created above will be our single source of truth to fetch data for the block. It already satisfies this for the client-side; let's now see do it for the server-side.

The Internal GraphQL Server extension installs a server that can be invoked within our application, using PHP code.

🔥 Tips:

the Internal GraphQL Server provides the following static methods, via class GraphQLServer:

  • executeQuery: Execute a GraphQL query
  • executeQueryInFile: Execute a GraphQL query contained in a (.gql) file
  • executePersistedQuery: Execute a persisted GraphQL query (providing its ID as an int, or slug as a string)

The signature of executeQueryInFile looks like this:

namespace GatoGraphQL\InternalGraphQLServer;

class GraphQLServer {
/**
* Execute a GraphQL query contained in a (`.gql`) file
*/

public static function executeQueryInFile(
string $file,
array $variables = [],
?string $operationName = null
): Response {
// ...
}
}

By invoking executeQueryInFile passing the .gql file created earlier on, we retrieve the data when rendering the dynamic block:

use GatoGraphQL\InternalGraphQLServer\GraphQLServer;

$block = [
'render_callback' => function(array $attributes, string $content): string {
// Provide the GraphQL query file
$file = __DIR__ . '/blocks/my-block/graphql-documents/fetch-posts-with-author.gql';

// Execute the query against the internal server
$response = GraphQLServer::executeQueryInFile($file);

// Get the content and decode it
$responseContent = json_decode($response->getContent(), true);

// Access the data and errors from the response
$data = $responseContent["data"] ?? [];
$errors = $responseContent["errors"] ?? [];

// Do something with the data
// $content = $this->useGraphQLData($content, $data, $errors);
// ...

return $content;
},
];
register_block_type("namespace/my-block", $block);

This way, a single .gql file retrieves the data to power blocks on both the client and server-sides.