Leonardo Losoviz Freelance developer and writer, with an ongoing quest to integrate innovative paradigms into existing PHP frameworks, and unify all of them into a single mental model.

Coding a GraphQL server in JavaScript vs. WordPress

12 min read 3450

Comparing GraphQL Server in Javascript vs. Wordpress

GraphQL is a specification, not an implementation for a specific language. As such, there are GraphQL servers for many languages, including JavaScript, PHP, Rust, Python, Go, and others.

graphql-js, the first GraphQL server, is coded in JavaScript. It is also the reference implementation of the GraphQL specification, so other GraphQL servers may follow the architectural decisions set by graphql-js for their own solutions.

Different languages have different characteristics, however, and porting the solution for a JavaScript-based server might not produce optimal results for servers based on other languages, or with a different philosophy on how the application is expected to work. Instead, having the room to deviate from these guidelines might provide better results.

For this write-up, I will explore how building a GraphQL solution for WordPress may diverge from the standard JavaScript guidelines on three circumstances:

  1. Extending the query resolution
  2. Namespacing the schema (or not)
  3. Using nested mutations (or not)

I will use my own plugin GraphQL API for WordPress to demonstrate the WordPress approach.

For the comparison, I will draw on the different characteristics between JavaScript and PHP, on the underlying philosophies driving their communities, and on the accepted concepts and standard ways employed when working with these technologies.

I will also explore whether a PHP-based GraphQL server could be able to offer custom features, which cannot be part of the GraphQL specification because graphql-js cannot support them.

Let’s start.

1. Making the query resolution extensible through directives

A WordPress application is highly extensible through plugins. Within the context of GraphQL, we may want plugins be able to modify:

  • The schema — adding custom types, fields to existing types, and directives
  • The query resolution — overriding the value for a field

In this section, we’ll explore the second element: How could the GraphQL service support modifying the results of the query?

We made a custom demo for .
No really. Click here to check it out.

The WordPress solution

WordPress uses hooks to modify behavior. Hooks are simple pieces of code that can override a value or execute an action whenever triggered.

In this example, function getBlockCategories reacts to filter block_categories to modify the block categories enabled in the WordPress editor:

class AbstractBlockCategory
{
  function initialize()
  {
    \add_filter(
      'block_categories',
      [$this, 'getBlockCategories']
    );
  }

  public function getBlockCategories(array $categories): array
  {
    return [
      ...$categories,
      [
        'slug' => 'graphql-api-access-control',
        'title' => __('Access Control for GraphQL', 'graphql-api'),
      ],
    ];
  }
}

Hooks are simple, versatile, and powerful; they can be abused, but when well implemented, they make the application highly extensible in ways that the developer did not plan in advance.

Directives as hooks

Influenced by this WordPress solution, I’ve searched for a similar solution for GraphQL and came to the conclusion that directives can be considered their equivalent.

A directive is like a WordPress hook: it is a function that modifies the value of a field, thus augmenting some other functionality. As its counterpart, it is simple, versatile, and powerful.

For instance, let’s say we retrieve a list of post titles with this query:

query {
  posts {
    title
  }
}

These results are in English. How can we translate them to French? A directive @translate applied on field title, which gets the value of the field as an input, calls the Google Translate API to translate the title and returns this output, as in this query:

query {
  posts {
    title @translate(from:"en", to:"fr")
  }
}

I find the use case for extensibility quite clear: given a value for field title, modify it in any desired way through a directive. In this case, its modification is the translation to French through @translate, but it could also be converting it to upper-/lowercase through @upperCase and @lowerCase, or anything else.

But does the JS community share my convictions?

Directives in JS

I believe my previous solution is not widely accepted within the (mostly working with JS) GraphQL community.

When describing this solution, I would be rebuked on how terrible query directives are, as in this comment on Reddit:

The presence of extensive directives on the query side feels like “code smell” to me

That comment thread ended up in a fight, which I’m not proud of 😔. It’s incredible how even directives can become a motive of allegiance, hostility and contention! 🤣

Using field arguments instead of directives

The alternative solution I was offered is to use arguments in the field itself, like this:

query {
  posts {
    title(translateFrom: "en", translateTo: "fr")
  }
}

I’ve come across this solution being exhorted in other places as well — for instance, here.

The issue with field arguments as replacement for directives

For me, using field arguments is a poor solution; it is mixing the logic from translation with the logic of resolving a field, all within that field resolver. It might work, but it is not particularly elegant.

Also, what happens if we need to translate the excerpt, too? It’s very easy with directives since they can be readily applied to any field without any change, as in this query:

query {
  posts {
    title @translate(from:"en", to:"fr")
    excerpt @translate(from:"en", to:"fr")
  }
}

As a field argument, though, the translation logic would also need to “pollute” the resolution for field excerpt:

query {
  posts {
    title(translateFrom: "en", translateTo: "fr")
    excerpt(translateFrom: "en", translateTo: "fr")
  }
}

Analyzing what works for extensibility as expected by WordPress

Different developers working with different technologies will have different ways of architecting solutions, and as long as they work, there’s no issue — there is no right or wrong.

However, concerning the specific topic at hand (extensibility of resolving GraphQL queries), will using field arguments to replace directives always work? Or only when all information is known in advance, as when coding a GraphQL API for one’s own company, or for a specific client?

Would it work given the WordPress expectation to make the service always extensible, even if we don’t know which other plugin will be extending it, or what custom requirements may the user have?

And given WordPress’ extreme user-friendliness, which means that functionality can be enabled through the user interface, could the GraphQL schema be extended without touching a line of code?

No, I don’t think it will: using field arguments, a developer will always need to know what integration must be done on the GraphQL schema, and it must be carried out through code. Hence, this methodology is not suitable for the WordPress way of doing things.

Using directives, though, all logic is cleanly and elegantly decoupled, extensibility can be achieved on the query itself, and it can even be integrated to the schema through configuration, and via a user interface (in which case we could even avoid exposing the query directives to the client side).

Where does the different approach come from?

I don’t think this is an issue of JavaScript vs. PHP/WordPress as technologies, but about their underlying philosophies and methodologies of work. While JS is more developer-friendly, WordPress would rather prioritize the needs of the end user over convenience for the developer.

(As a side note, a similar discussion has been raging on lately regarding a supposed rivalry between WordPress and the Jamstack, about which I have my own opinion.)

As for myself, when I promote solutions based on directives, I’m coming from the “make it extensible through hooks” philosophy that has so well served WordPress, and could serve GraphQL equally well too.

This philosophy has been deeply embedded within plugin GraphQL API for WordPress: it uses a directive pipeline to resolve queries, and directives are treated as first-class citizens. Among others, they can authenticate users, remove the output for a field, and stop the execution of the upcoming stages on the pipeline.

2. Avoiding conflicts among plugins via namespacing

Developers publishing plugins on the WordPress directory do not know in advance who will use their plugins or what configuration/environment the site will have, including what other plugins may be installed. As a consequence, the plugin must be prepared for conflicts and attempt to prevent them beforehand.

How do WordPress plugins avoid conflicts?

Namespacing in PHP

Let’s talk again about hooks. If my plugin needs to add a filter to modify the user’s name, the filter cannot be simply called "userName" since this name is too generic and I risk another plugin using the same name. Instead, I must prefix it with some unique name, such as "GraphQLAPIForWP:userName".

Namespacing serves the same role — to make some code unique.

Namespaces are widely used within the PHP community, following the PHP Standard Recommendation PSR-4 to enable Composer autoloading. PHP packages must include the vendor’s name, as "vendor-name/package-name", and the corresponding namespace is present on the PHP code:

<?php
namespace VendorName\PackageName;

Would namespacing make sense for a GraphQL service, too?

Namespacing in GraphQL

Namespacing could make sense within the context of GraphQL as well to avoid the following potential conflicts happening on the schema:

  • Having two types with the same name
  • Having two fields on the same type with the same name
  • Having two directives with the same name

Namespacing has actually been requested for the GraphQL spec:

At the moment, all GraphQL types share one global namespace. This is also true for all of the fields in mutation/subscription type. It can be a concern for bigger projects, which may contain several loosely coupled parts in a GraphQL schema.

This issue, which was opened more than four years ago, still remains open. Lee Byron (one of the creators of GraphQL while working at Facebook) has expressed his opposition to this feature. In this comment, he explains how Facebook manages the thousands of types in its GraphQL schema without conflict:

We avoid naming collisions in two ways:

  1. Integration tests. We don’t allow any commit to merge into our repository that would result in a broken GraphQL schema. […]
  2. Common naming patterns. We have common patterns for naming things, which naturally avoid collision problems. […]

But I have to wonder, will what works for Facebook necessarily work for WordPress, too?

The Facebook context vs. the WordPress context

Please notice that in this section, WordPress is not being compared against JavaScript but against Facebook. However, since Facebook is one of the biggest stakeholders pushing GraphQL forward, deeply influencing the design decisions for graphql-js, I think the comparison is valid.

Lee Byron claims that if Facebook does not need namespacing to manage its thousands of types, then we can all do without it.

There is an important factor at play here, however: Facebook controls all inputs to its GraphQL schema! In this case, it can afford to follow a procedure to name the entities, making sure that no conflict arises.

But this isn’t how WordPress works; the WordPress site relies heavily on third-party plugins, and it does not control how these plugins are produced.

For instance, if a site uses the WooCommerce and Easy Digital Downloads plugins, and they both have a type named Product for the GraphQL schema, there will be a conflict. The only resource for the site owner is to reach out to one of the companies and ask them to modify their code. This is not prevention but correction, and it’s unreliable.

Will conflicts really happen?

I’ve been speaking theory so far, but let’s consider this topic in practice: Will conflicts really happen?

WordPress plugins must pay serious mind to preventing naming conflicts because of the sheer number of available plugins (over 58,000 to date in the directory alone). The chances of some overlap happening are non-negligible.

But how popular is GraphQL within WordPress?

Unfortunately, its use is not yet very widespread. Unlike REST (which is built into WordPress core through the WP REST API), GraphQL must be installed from a third-party plugin. As a consequence, other than agencies creating themes and plugins for their clients, or when coding a personal site, developers can’t make the assumption that there will be a GraphQL service running on the WordPress site.

Currently, there are two GraphQL solutions for WordPress:

We are concerned about plugins extending the GraphQL schema. How many are there?

Searching on the WordPress plugin directory produces only a handful of results, none with more than 200+ activations.

Searching on GitHub produces the following WPGraphQL extensions with 10 stars or more:

(There are no extensions for the GraphQL API for WordPress yet.)

So then, if GraphQL is not so popular within WordPress yet, do we need namespacing?

Namespacing nevertheless

I think we do need namespacing, even if it won’t solve any problem right now. This is because:

  • It provides a built-in solution to avoid conflicts, acting as a promise for the future so that plugin developers have peace of mind that their code will always work
  • It makes the GraphQL schema more elegant

The last item has the following logic: if WooCommerce wanted to be sure no conflict will ever arise, then it can’t use generic names for their types, such as Product, Download, or Payment. Instead, it would need to “namespace” them on the type name itself: WCProduct, WCDownload, and WCPayment. Or, to be absolutely confident it will always work, it might call them WooCommerceProduct, WooCommerceDownload, and WooCommercePayment.

Well, the schema then became more verbose, less elegant. That is something that should be avoided, if possible.

Supporting namespacing automatically, as an opt-in

Given that we may not really need namespacing, the best strategy for the GraphQL server is to provide it as an opt-in feature that, when enabled, will automatically namespace all types in the schema.

This strategy is feasible in the GraphQL API for WordPress plugin because:

  • It uses the code-first approach (instead of the SDL-first approach) so that the schema is produced as an artifact of the code and can be conveniently regenerated
  • It references types through their PHP classes and not through the type names so that names can be conveniently overridden

For instance, in this code, field Post.comments declares being of type Comment by referencing its corresponding PHP class CommentTypeResolver:

class CustomPostFieldResolver extends AbstractQueryableFieldResolver
{
  // ...

  public function resolveFieldTypeResolverClass(
    TypeResolverInterface $typeResolver,
    string $fieldName,
    array $fieldArgs = []
  ): ?string {
    switch ($fieldName) {
      case 'comments':
        return CommentTypeResolver::class;
    }

    return null;
  }
}

As a further enhancement, the plugin needs no input from the user to namespace types. Instead, the existing PSR-4 naming for all PHP packages is used, transforming "VendorName\PackageName" to "VendorName_PackageName" for their types in the schema (neither characters "\" or "/" are currently allowed in the syntax for type names, so "_" is used instead).

3. Deviating from the standards: Nested mutations

Mutations are operations that can alter data in the GraphQL server, such as when creating a post, updating the user’s name, adding a comment to a post, and so on.

In GraphQL, mutations are exposed under the RootMutation type only, like this:

type RootMutation {
  createPost(id: ID!, title: String!, content: String): Post!
  updateUserName(userID: ID!, newName: String!): User!
  addCommentToPost(postID: ID!, comment: String!, userID: ID): Comment!
}

With this schema, modifying the user’s name is achieved like this:

mutation {
  updateUserName(userID: 37, newName: "Peter") {
    name
  }
}

Mutations are exposed in the mutation root object type only to enforce that they are executed serially, as explained in the GraphQL spec:

It is expected that the top-level fields in a mutation operation perform side effects on the underlying data system. Serial execution of the provided mutations ensures against race conditions during these side effects.

The term “serial execution” is opposed to “parallel execution,” which is otherwise the recommended behavior to resolve fields.

For instance, in the query below, it doesn’t matter which field (whether name or email) the GraphQL server resolves first, and these can be resolved in parallel:

query {
  user(id: 37) {
    name
    email
  }
}

Mutations alter data, though, so the order in which fields are resolved does matter; thus, they must be executed serially, or otherwise, they could produce race conditions.

For instance, the two queries below will produce different results:

# Query 1: after execution, user name will be "John"
mutation {
  updateUserName(userID: 37, newName: "Peter") {
    name
  }
  updateUserName(userID: 37, newName: "John") {
    name
  }
}

# Query 2: after execution, user name will be "Peter"
mutation {
  updateUserName(userID: 37, newName: "John") {
    name
  }
  updateUserName(userID: 37, newName: "Peter") {
    name
  }
}

The consequence of exposing mutations only through RootMutation is that this type becomes heavily bloated, containing fields with nothing in common among themselves other than having to be executed serially (which is a technical matter, not an interface design decision).

The case for nested mutations

From the mutations above, only createPost truly lives under the RootMutation type because it is creating a new element out of nowhere. Mutations updateUserName and addCommentToPost, though, can have equivalent operations applied on an existing entity from another type:

type User {
  updateName(newName: String!): User!
}

type Post {
  addComment(comment: String!, userID: ID): Comment!
}

With this schema, modifying the user’s name could be achieved like this:

mutation {
  user(ID: 37) {
    updateName(newName: "Peter") {
      name
    }
  }
}

This feature is called nested mutations — applying a mutation to the result of another operation, whether a query or mutation.

Please notice how using nested mutations makes the GraphQL schema more elegant:

  • While operation RootMutation.updateUserName must receive the ID of the user, its equivalent operation User.updateName must not since it is already executed on a user entity
  • Field name is shortened from updateUserName to updateName

In addition, the GraphQL service becomes simpler and more understandable since we can navigate among entities in the graph to modify their data in the same way as to query their data.

Nested mutations can go down multiple levels. For instance, we can add a comment on a newly created post, all within a single query:

mutation {
  createPost(ID: 37, title: "Hello world!", content: "Just another post") {
    id
    addComment(comment: "Lovely post") {
      id
    }
  }
}

From this, nested mutations can also improve performance by reducing round-trip latency, from executing multiple queries to mutate several elements to executing a single query.

Nested mutations have been requested for the GraphQL spec almost four years ago, but the issue remains open (for reasons we’ll explore below).

Why nested mutations are not part of the spec

The GraphQL spec is meant to work for all implementations of GraphQL servers for any language. However, its driving force is JavaScript through graphql-js, the reference implementation.

In other words, any feature that cannot be supported by graphql-js will not become part of the specification.

Since JavaScript supports promises, parallel resolution of fields was feasible, and parallelism became one of the fundamental principles when first designing graphql-js, as manifest in DataLoader (the data fetching layer), whose batching functions return JavaScript promises.

The advantages of parallel execution for performance are too many, and nested mutations cannot work with parallelism. It has been decided that it would not be worth trading parallel execution for nested mutations. Hence, nested mutations will not make it to the spec.

But what happens for other languages that don’t support promises?

Why nested mutations make sense for PHP

PHP does not naturally support promises (unless using an external library, such as ReactPHP). Therefore, supporting nested mutations would not produce the negative consequence of removing parallel execution since this feature doesn’t exist in the first place.

This means that a PHP-based GraphQL server should be perfectly capable of supporting nested mutations, providing all of its benefits, and suffering none of its consequences.

It is an opportunity. Why shouldn’t it be taken?

Nested mutations and performance

For the GraphQL API for WordPress plugin, fields are always resolved serially, and the order in which they are resolved is deterministic (it is this trait that makes it possible to support the @export directive).

This characteristic does not affect the query resolution performance because the server uses queues (instead of a graph) when resolving queries, delivering a complexity time O(n) based on the number of types on the schema. Resolving a GraphQL query with linear time is as good as it can get.

As I’m writing this article, the implementation of nested mutations for this plugin is a work in progress. Since nested mutations are not supported by the GraphQL spec, this feature will be added as opt-in, with a disclaimer that it is non-standard functionality.

Conclusion

In this article we’ve explored how different factors may affect the implementation of certain elements of a GraphQL server for WordPress as compared to its equivalent server for JavaScript:

  1. Different methodologies used when working with WordPress/JS may affect the architecture to provide an extensible query resolution system.
  2. Different philosophies on how and when the GraphQL schema can be modified, and by whom, may affect whether introducing namespacing to the schema is necessary.
  3. Different characteristics between JavaScript and PHP may justify supporting nested mutations.

There are no good or bad solutions, just solutions that work better for different contexts. I have proposed and demonstrated solutions for WordPress, which, taking into account its philosophy, its opinionatedness, the capabilities from PHP, and others, I believe work better than the JavaScript-based alternative.

Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Leonardo Losoviz Freelance developer and writer, with an ongoing quest to integrate innovative paradigms into existing PHP frameworks, and unify all of them into a single mental model.

Leave a Reply