Docs
Launch GraphOS Studio
Since 1.8.0

Apollo Router support for @defer

Improve performance by delivering fields incrementally


Queries sent to the Apollo can use the @defer to enable the incremental delivery of response data. By deferring data for some s, the can resolve and return data for the query's other s more quickly, improving responsiveness.

The Apollo 's @defer support is compatible with all federation-compatible subgraph libraries. That's because the takes advantage of your 's existing entities to fetch any deferred data via followup queries to your s.

What is @defer?

The @defer enables a client query to specify sets of s that it doesn't need to receive data for immediately. This is helpful whenever some s in a query take much longer to resolve than others.

Deferred s are always contained within a GraphQL , and the @defer is applied to that (not to the individual s).

Here's an example query that uses @defer:

query GetTopProducts {
topProducts {
id
name
... @defer {
price
}
}
}

To respond incrementally, the Apollo uses a multipart-encoded HTTP response. To use @defer successfully with the Apollo , a client's GraphQL library must also support the by handling multipart HTTP responses correctly.

The Apollo 's @defer support is compatible with all federation-compatible subgraph libraries, because the deferring logic exists entirely within the itself.

Basics of @defer

To learn the basics of the @defer and how you can use it with your , first read Deferring query response data with GraphOS.

The remainder of this article covers the Apollo 's defer implementation in greater depth.

How does the Apollo Router defer fields?

As discussed in this article, the Apollo can defer the following s in your :

  • Root s of the Query type (along with their subs)
  • s of any entity type (along with their subfields)

The can defer specifically these s because they are all entry points into one of your s. This enables the to incorporate the deferral directly into its generated query plan.

Query plan example

Consider a with these s:

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int!
}
type Query {
topProducts: [Product!]!
}
Reviews subgraph
type Product @key(fields: "id") {
id: ID!
reviews: [Review!]!
}
type Review {
score: Int!
}

And consider this query executed against that :

query GetTopProductsAndReviews {
topProducts { # Resolved by Products subgraph
id
name
reviews { # Resolved by Reviews subgraph
score
}
}
}

To resolve all of these s, the needs to query both the Products and the Reviews . Not only that, but the specifically needs to query the Products subgraph first, so that it knows which products to fetch reviews for.

When the receives this query, it generates a sequence of "sub-queries" that it can run on its s to resolve all requested s. This sequence is known as a query plan.

Here's a visualization of the query plan for the example query:

Fetch (products)
Fetch (reviews)
Flatten (topProducts,[],reviews)

This query plan has three steps:

  1. The queries the Products to retrieve the id and name of each top product.
  2. The queries the Reviews providing the id of each top product—to retrieve corresponding review scores for those products.
  3. The combines the data from the two sub-queries into a single response and returns it to the client.

Because the second sub-query depends on data from the first, these two sub-queries must occur serially.

But the result of the first sub-query includes a significant portion of the data that the client requested! To improve responsiveness, the could theoretically return that portion as soon as it's available.

A defer-compatible client can request exactly this behavior with the @defer :

query GetTopProductsAndDeferReviews {
topProducts {
id
name
... @defer {
reviews {
score
}
}
}
}

With this query, the understands that it can return the result of its first sub-query as soon as its available, instead of waiting for the result of the second sub-query. Later, it returns the result of the second sub-query when it's ready.

Remember, the router can defer the Product.reviews field specifically because it's a field of an entity. Query plans already use entity s as entry points for their sub-queries, and the takes advantage of this behavior to power its defer support.

Deferring within a single subgraph

In the previous example, a client defers s in a query that already requires executing multiple sub-queries. But what if all of a client query's s belong to a single ?

Consider this client query:

query GetTopProducts {
topProducts { # All fields resolved by Products subgraph
id
name
price
}
}

Because all of these requested s are defined in a single , by default the generates the most basic possible query plan, with a single step:

Fetch (products)

Now, let's imagine that the Product.price takes significantly longer to resolve than other Product s, and a querying client wants to defer it like so:

query GetTopProducts {
topProducts {
id
name
... @defer {
price
}
}
}

This is valid! When the sees this defer request, it generates a different query plan for the query:

Fetch (products)
Fetch (products)
Flatten (topProducts,[],price)

Now, the queries the same subgraph twice, first to fetch non-deferred s and then to fetch the deferred fields. When the first sub-query returns, the can immediately return each product's id and name to the client while sending a followup sub-query to fetch price information.

Non-deferrable fields

A query's @defer might include s that the Apollo can't defer. The handles this case gracefully with the following logic:

  • The defers every in the that it can defer.
  • The resolves any non-deferrable s in the before sending its initial response to the client.
  • The 's response to the client still uses multipart encoding to separate @defer s from other fields, even if some fragment fields couldn't be deferred.
    • This preserves the response structure that the client expects based on its use of @defer.

Example

To illustrate a non-deferrable , let's look at an example using this :

type Book @key(fields: "id") {
id: ID!
title: String!
author: Author!
}
type Author {
name: String!
books: [Book!]!
}
type Query {
books: [Book!]!
authors: [Author!]!
}

Note in this that the Book type is an entity and the Author type is not.

Let's say a client executes the following query:

query GetAuthors {
authors {
name
... @defer {
books { # Can't be deferred
title # CAN be deferred
}
}
}
}

This query attempts to defer two s: Author.books and Book.title.

  • Author.books is neither a root Query nor an entity field (Author is not an entity), so the can't defer it.
  • Book.title is the of an entity type, so the can defer it.
    • If Book.title had any subs, the could also defer those s.

In this case, the must internally resolve each author's list of associated books before it can send its initial response to the client. Later, it can resolve each book's title and return those Book objects to the client in an incremental part of the response.

Specification status

The @defer is currently part of a draft-stage RFC for the GraphQL specification (learn about RFC contribution stages).

The Apollo supports the @defer as it's documented in these edits to the RFC, according to the state of those edits on 2022-08-24.

Disabling @defer

Defer support is enabled in the Apollo by default. To disable support, add defer_support: false to your 's YAML config file under the supergraph key:

router.yaml
supergraph:
defer_support: false
Previous
Build and run queries
Next
Request format
Edit on GitHubEditForumsDiscord