@defer patterns and the response parts that each one produces.
The responses come from the router’s test suite, so they show exactly what the router sends.
Defer describes the multipart framing.
This page shows only the JSON payload of each part.
Schema used on this page
The examples run against the Cosmo demo, a federated graph with anEmployee entity spread over several subgraphs.
Employee.derivedMood declares @requires(fields: "currentMood"), so it depends on the mood subgraph.
Defer a fragment from another subgraph
This is the most common pattern. The router returns the fields from a fast subgraph first. The fields from a slow subgraph follow.pending entry tells the client that the path of fragment 1 is ["employee"].
The incremental entry delivers hobbies for that path.
The position of the @defer fragment inside the selection set does not matter.
A fragment before details produces the same two parts.
Defer the whole query
A@defer fragment at the root selection set defers every field.
The initial response is then empty.
This is rarely useful on its own.
The router allows it, because if variables can turn individual fragments on and off.
Different variable values then produce different response shapes.
data is an empty object, and path is empty, because the deferred fragment is on the root.
Nested @defer
A client can nest a fragment inside another deferred fragment. The router delivers the inner fragment after the outer one.1.
The router announces fragment 2 in the first incremental response, together with the delivery of its parent.
A client therefore updates its pending map from every part, not only from the first.
Sibling @defer fragments
Two fragments at the same level are independent. The router delivers each one in its own part as soon as it is ready.hasNext: false.
The fragments run concurrently.
The arrival order of the two parts depends on which subgraph answers first.
A client matches a part to a fragment by id, never by arrival order.
@defer inside a list
The router announces a fragment inside a list field once, and delivers it once for all list items. Each item gets its ownincremental entry, with its own subPath.
products has three items, and one of them is a Consultancy.
The pending entry points at the list, ["products"].
The incremental entry adds [0] as subPath, so the client merges it into products[0].
The list items appear as {} in the initial response, because the fragment covers all of their selected fields.
A list with several matching items produces several incremental entries in the same part.
Each entry carries its own index.
Fields selected both inside and outside @defer
The router delivers a field selected outside a deferred fragment in the initial response. This also applies when the same field is inside the fragment.details.forename is in the initial response.
The router defers details.location.
It arrives with subPath: ["details"], because it belongs to an object below the path of the fragment.
One fragment can therefore produce several incremental entries in one part.
Named fragments
@defer works on fragment spreads and inside fragment bodies.
id only.
It announces one fragment for the whole list:
incremental entry per employee.
Each entry carries its list index as subPath.
A @defer inside the fragment body, for example around hobbies, has the same effect as an inline fragment at that position in every spread.
Labels
A label names a fragment in the response.pending entry only.
incremental and completed entries reference the fragment by id.
A label is a static string.
A label must be unique across all @defer and @stream directives in the document.
Conditional @defer
Theif argument turns deferral on and off per request.
{"deferHobbies": true}, the response is a multipart response with two parts.
With {"deferHobbies": false}, the router returns one JSON response with Content-Type: application/json.
data then contains hobbies:
@defer.
Header propagation
Header forwarding rules apply to every subgraph request of an operation. They also apply to the requests for deferred fragments. A header from the client request reaches the deferred subgraphs and the primary subgraphs in the same way.Clients
curl
-N disables output buffering, so curl prints each part when it arrives.
The output shows the raw multipart stream:
-i to print the response headers as well, including Content-Type: multipart/mixed; boundary="graphql"; incrementalSpec=v0.2.
fetch
The browserfetch API exposes the response as a stream.
The following example reads the stream and splits it at the part boundary.
It prints the JSON payload of each part when the part arrives.
done.
To build the complete result, merge each incremental entry at pending[id].path, followed by subPath.
Payload fields describes this merge.
Apollo Client
Apollo Client 4.1 and newer supports the response format the router sends. Configure theGraphQL17Alpha9Handler as the incremental handler:
Accept: multipart/mixed;incrementalSpec=v0.2 for operations that contain @defer.
The router accepts that header.
A query with @defer then delivers partial data to useQuery and watchQuery as parts arrive.
dataState moves from streaming to complete.
Apollo Client rewrites the operation before it sends the operation.
It adds a generated label such as ac_0 to every @defer fragment.
The router returns those labels on the corresponding pending entries.
The Defer20220824Handler implements the older format of Apollo Router.
It sends Accept: multipart/mixed;deferSpec=20220824.
That handler merges the deferred data at the wrong place.
The router therefore rejects such a request with the error code DEFER_UNSUPPORTED_SPEC.
See Send a request for the error body.
Use GraphQL17Alpha9Handler.