Executing multiple queries concurrently [PRO]

📣 Note: This functionality will be available soon (in the PRO version).

Multiple queries can be combined together, and executed as a single operation, reusing their state and their data. In this case, if a first query fetches some data, and a second query also accesses the same data, this data is retrieved only once from the database, not twice.

✳️ Note: This is different from query batching, in which the GraphQL server also executes multiple queries in a single request, but those queries are merely executed one after the other, independently from each other.

This feature improves performance. Instead of executing queries independenty in different requests (so that we first execute an operation against the GraphQL server, then wait for its response, and then use that result to perform another operation), we can execute them together, thus avoiding the latency from the several requests.

How to use multiple query execution permalink

Let's suppose we want to search all posts which mention the name of the logged-in user. Normally, we would need two queries to accomplish this:

We first retrieve the user's name:

query GetLoggedInUserName {
me {
name
}
}

...and then, having executed the first query, we can pass the retrieved user's name as variable $search to perform the search in a second query:

query GetPostsContainingString($search: String = "") {
posts(filter: { search: $search }) {
id
title
}
}

Multiple Query Execution simplifies this process, allowing us to retrieve all data and execute all required logic in a single request:

query GetLoggedInUserName {
me {
name @export(as: "search")
}
}

query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}

Multiple Query Execution is attained with the use of two special directives:

  • @depends (operation directive): have an operation (whether a query or mutation) indicate what other operations must be executed before
  • @export (field directive): export some field value from one operation, to inject it as an input to some field in another operation

The GraphQL server will create the list of operations to load and execute, retrieving them from each @depends(on: ...), and will export the values from any field containing @export as a dynamic variable (with name defined under argument as) to be input in any subsequent operation.

Combining these directives, we are able to split any complex functionality into intermediate steps, alternating query and mutation operations, add their dependencies in the required order, and execute them all in a single request by defining the outermost operation in ?operationName=... (in the example above, that will be ?operationName=GetPostsContainingString).

Defining the operations to load and execute via @depends permalink

When the GraphQL Document contains multiple operations, we must provide URL param ?operationName= to indicate the server which one to execute.

Starting from this initial operation, the server will collect all operations to execute, which are defined by adding directive depends(on: [...]), and execute them in the corresponding order respecting the dependencies.

Directive arg operations receives an array of operation names ([String]), or we can also provide a single operation name (String).

In this query, we pass ?operationName=Four, and the executed operations (whether query or mutation) will be ["One", "Two", "Three", "Four"]:

mutation One {
# Do something ...
}

mutation Two {
# Do something ...
}

query Three @depends(on: ["One", "Two"]) {
# Do something ...
}

query Four @depends(on: "Three") {
# Do something ...
}

Sharing data across queries via @export permalink

Directive @export exports the value of a field (or set of fields) into a dynamic variable, to be used as input in some field from another query.

For instance, in this query we export the logged-in user's name, and use this value to search for posts containing this string (please notice that variable $loggedInUserName, because it is dynamic, does not need be defined in operation FindPosts):

query GetLoggedInUserName {
loggedInUser {
name @export(as: $loggedInUserName)
}
}

query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}

Exporting data permalink

@export handles these 4 cases:

  1. Exporting a single value from a single field
  2. Exporting a list of values from a single field

In addition, when the Multi-Field Directives feature is enabled, @export handles 2 additional cases:

  1. Exporting a dictionary of values, containing several fields from the same object
  2. Exporting a list of a dictionary of values, with each dictionary containing several fields from the same object

1. Exporting a single value from a single field permalink

@export handles exporting a single value from a single field.

For instance, the user's name field, with type String, is exported:

query GetLoggedInUserName {
me {
name @export(as: "search")
}
}

query GetPostsContainingString {
posts(filter: { search: $search }) {
id
title
}
}

2. Exporting a list of values from a single field permalink

@export handles exporting lists.

For instance, list of names from the logged-in user's friends, with type [String], are exported (hence the type of the $search variable went from String to [String]):

query GetLoggedInUserFriendNames {
me {
friends {
name @export(as: "search")
}
}
}

query GetPostsContainingLoggedInUserFriendNames {
posts(filter: { searchAny: $search }) {
id
title
}
}

3. Exporting a dictionary of values, containing several fields from the same object permalink

@export handles exporting several properties from a same object, creating a dictionary of values containing the exported properties.

For instance, both the name and surname fields from the user are exported, creating an object which contains these 2 properties:

query GetLoggedInUserNameAndSurname {
me {
name
surname
@export(
as: "search"
affectAdditionalFieldsUnderPos: [1]
)
}
}

query GetPostsContainingLoggedInUserNameAndSurname {
posts(filter: { searchByAnyProperty: $search }) {
id
title
}
}

4. Exporting a list of a dictionary of values, with each dictionary containing several fields from the same object permalink

@export handles exporting several properties from a list of objects, creating a list of dictionaries of values containing the exported properties.

For instance, fields name and surname from the list of the logged-in user's friends are exported:

query GetLoggedInUserFriendNamesAndSurnames {
me {
friends {
name
surname
@export(
as: "search"
affectAdditionalFieldsUnderPos: [1]
)
}
}
}

query GetPostsContainingLoggedInUserFriendNamesAndSurnames {
posts(filter: { searchAnyByAnyProperty: $search }) {
id
title
}
}

Conditional execution of operations permalink

When Multiple Query Execution is enabled, directives @include and @skip are also available as operation directives, and these can be use to conditionally execute an operation if it satisfies some condition.

For instance, in this query, operation CheckIfPostExists exports a dynamic variable $postExists and, only if its value is true, will mutation ExecuteOnlyIfPostExists be executed:

query CheckIfPostExists($id: ID!) {
# Initialize the dynamic variable to `false`
postExists: _echo(value: false) @export(as: "postExists")

post(by: { id: $id }) {
# Found the Post => Set dynamic variable to `true`
postExists: _echo(value: true) @export(as: "postExists")
}
}

mutation ExecuteOnlyIfPostExists
@depends(on: "CheckIfPostExists")
@include(if: $postExists)
{
# Do something...
}

Directive execution order permalink

If there are other directives before @export, the exported value will reflect the modifications by those previous directives.

For instance, in this query, depending on @export taking place before or after @upperCase, the result will be different:

query One {
id
# First export "root", only then will be converted to "ROOT"
@export(as: "id")
@upperCase

again: id
# First convert to "ROOT" and then export this value
@upperCase
@export(as: "again")
}

query Two @depends(on: "One") {
mirrorID: _echo(value: $id)
mirrorAgain: _echo(value: $again)
}

Producing:

{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}

Multi-Field Directives permalink

When the Multi-Field Directives feature is enabled and we export the value of multiple fields into a dictionary, use @deferredExport instead of @export to guarantee that all directives from every involved fields have been executed before exporting the field's value.

For instance, in this query, the first field has directive @upperCase applied to it, and the second has @titleCase. When executing @deferredExport, the exported value will have these directives applied:

query One {
id @upperCase # Will be exported as "ROOT"
again: id @titleCase # Will be exported as "Root"
@deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}

query Two @depends(on: "One") {
mirrorProps: _echo(value: $props)
}

Producing:

{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}

GraphQL spec permalink

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