DEV Community

Leonardo Losoviz
Leonardo Losoviz

Posted on • Updated on

🚀 Executing multiple queries in a single operation in GraphQL

Update 23/01/2021: I created the guides for the Multiple Execution Query in graphql-api.com:


This post was originally published on my blog leoloso.com

It's been only 15 days since releasing the GraphQL API for WordPress, and I couldn't help myself, so this week I added yet a new feature: the server can now execute multiple queries in a single operation.

Executing queries independently, and then all together as a single operation

This is not query batching. When doing query batching, the GraphQL server executes multiple queries in a single request. But those queries are still independent from each other. They just happen to be executed one after the other, to avoid the latency from multiple requests.

In this case, all queries are combined together, and executed as a single operation. That means that they will reuse their state and their data. For instance, if a first query fetches some data, and a second query also accesses the same data, this data is retrieved only once, not twice.

This feature is shipped together with the @export directive, which enables to have the results of a query injected as an input into another query. Check out this GraphiQL client for the query below, hit "Run" and select query with name "__ALL", and see how the user's name obtained in the first query is used to search for posts in the second query:

# Export the user's name
query GetUserName {
  user(id:1) {
    name @export(as: "_search")
  }
}

# Search for posts with the user's name from the previous query
query SearchPosts($_search: String = "") {
  posts(searchfor: $_search) {
    title
  }
}
Enter fullscreen mode Exit fullscreen mode

How is this feature useful?

This functionality is currently not part of the GraphQL spec, but it has been requested:

This feature improves performance, for whenever we need to execute an operation against the GraphQL server, then wait for its response, and then use that result to perform another operation. By combining them together, we are saving this extra request.

You may think that saving a single roundtrip is no big deal. Maybe. But this is not limited to just 2 queries: it can be chained, containing as many operations as needed.

For instance, this simple example chains a third query, and adds a conditional logic applied on the result from a previous query: if the post has comments, translate the post's title to French, but if it doesn't, show the name of the user. Click on the "Run" button in this GraphiQL client, see the results, then change variable offset to 1, run the query again, and see how the results change:

# Export the user's name
query GetUserName() {
  user(id: 1) {
    name @export(as: "_search")
  }
}

# Search for posts with the user's name from the previous query
query SearchPosts($offset:Int = 0, $_search: String = "") {
  posts(searchfor: $_search, limit: 1, offset: $offset) {
    title @export(as: "_postTitles")
    hasComments @export(as: "_postHasComments")
  }
}

# Translate post titles, or show user ID if not
query TranslatePosts($_postTitles: [String] = [], $_postHasComments: Boolean! = false) {
  translatedTitle: echoVar(variable: $_postTitles) @include(if:$_postHasComments) @translate(from:"en", to:"fr")
  noTitleForUser: echoVar(variable: $_search) @skip(if:$_postHasComments)
}
Enter fullscreen mode Exit fullscreen mode

GraphQL as a (meta-)scripting language

As we've seen, we could attempt to use GraphQL to execute scripts, including conditional statements and even loops.

GraphQL by PoP, which is the GraphQL engine over which the GraphQL API for WordPress is based, is a few steps ahead in providing a language to manipulate the operations performed on the query graph.

For instance, I have implemented a query which allows to send a newsletter to multiple users, fetching the content of the latest blog post and translating it to each person's language, all in a single operation!

Check the query below, which is using the PoP Query Language, an alternative to the GraphQL Query Language:

/?
postId=1&
query=
  post($postId)@post.
    content|
    date(d/m/Y)@date,
  getJSON("https://newapi.getpop.org/wp-json/newsletter/v1/subscriptions")@userList|
  arrayUnique(
    extract(
      getSelfProp(%self%, userList),
      lang
    )
  )@userLangs|
  extract(
    getSelfProp(%self%, userList),
    email
  )@userEmails|
  arrayFill(
    getJSON(
      sprintf(
        "https://newapi.getpop.org/users/api/rest/?query=name|email%26emails[]=%s",
        [arrayJoin(
          getSelfProp(%self%, userEmails),
          "%26emails[]="
        )]
      )
    ),
    getSelfProp(%self%, userList),
    email
  )@userData;

  post($postId)@post<
    copyRelationalResults(
      [content, date],
      [postContent, postDate]
    )
  >;

  getSelfProp(%self%, postContent)@postContent<
    translate(
      from: en,
      to: arrayDiff([
        getSelfProp(%self%, userLangs),
        [en]
      ])
    ),
    renameProperty(postContent-en)
  >|
  getSelfProp(%self%, userData)@userPostData<
    forEach<
      applyFunction(
        function: arrayAddItem(
          array: [],
          value: ""
        ),
        addArguments: [
          key: postContent,
          array: %value%,
          value: getSelfProp(
            %self%,
            sprintf(
              postContent-%s,
              [extract(%value%, lang)]
            )
          )
        ]
      ),
      applyFunction(
        function: arrayAddItem(
          array: [],
          value: ""
        ),
        addArguments: [
          key: header,
          array: %value%,
          value: sprintf(
            string: "<p>Hi %s, we published this post on %s, enjoy!</p>",
            values: [
              extract(%value%, name),
              getSelfProp(%self%, postDate)
            ]
          )
        ]
      )
    >
  >;

  getSelfProp(%self%, userPostData)@translatedUserPostProps<
    forEach(
      if: not(
        equals(
          extract(%value%, lang),
          en
        )
      )
    )<
      advancePointerInArray(
        path: header,
        appendExpressions: [
          toLang: extract(%value%, lang)
        ]
      )<
        translate(
          from: en,
          to: %toLang%,
          oneLanguagePerField: true,
          override: true
        )
      >
    >
  >;

  getSelfProp(%self%,translatedUserPostProps)@emails<
    forEach<
      applyFunction(
        function: arrayAddItem(
          array: [],
          value: []
        ),
        addArguments: [
          key: content,
          array: %value%,
          value: concat([
            extract(%value%, header),
            extract(%value%, postContent)
          ])
        ]
      ),
      applyFunction(
        function: arrayAddItem(
          array: [],
          value: []
        ),
        addArguments: [
          key: to,
          array: %value%,
          value: extract(%value%, email)
        ]
      ),
      applyFunction(
        function: arrayAddItem(
          array: [],
          value: []
        ),
        addArguments: [
          key: subject,
          array: %value%,
          value: "PoP API example :)"
        ]
      ),
      sendByEmail
    >
  >
Enter fullscreen mode Exit fullscreen mode

(Please don't be shocked by this complex query! The PQL language is actually even simpler than GraphQL, as can be seen when put side-by-side.)

To run the query, there's no need for GraphiQL: it's URL-based, so it can be executed via GET, and a normal link will do. Click here and marvel: query to create, translate and send newsletter (this is a demo, so I'm just printing the content on screen, not actually sending it by email 😂).

What is going on there? The query is a series of operations executed in order, with each passing its results to the succeeding operations: fetching the list of emails from a REST endpoint, fetching the users from the database, obtaining their language, fetching the post content, translating the content to the language of each user, and finally sending the newsletter.

To check it out in detail, I've written a step-by-step description of how this query works.

Eventually in GraphQL?

You may think that you don't need to implement a newsletter-sending service. But that's not the point. The point is that, if you can implement this, you can implement pretty much anything you will ever need.

The query above uses a couple of features available in PQL but not in GQL, which I have requested for the GraphQL spec:

Sadly, I've been told that these features will most likely not be add to the spec.

Hence, GraphQL cannot implement the example, yet. But through executing multiple queries in a single operation, @export, and powerful custom directives, it can certainly support novel use cases.

Top comments (0)