Introducing support for the @defer directive

Graeme CouparGraeme CouparJamie BartonJamie Barton
Introducing support for the @defer directive

Grafbase now supports the @defer directive in GraphQL requests!

In GraphQL, the @defer directive facilitates marking certain query parts as non-essential for immediate return, allowing them to be deferred until key data has been processed and dispatched. This enables applications to act on critical data more promptly, considerably accelerating the time-to-interactive for applications.

It's worth noting that @defer isn't officially part of the GraphQL specification just yet, but it's going to happen! We made sure to follow the best practices set out in the DeferStream RFC so our implementation is inline with other providers who adopt the @defer directive.

Utilizing @defer locally with Pathfinder requires no extra steps — it "just works". However, when incorporating @defer within your own application, ensure to include the Accept: multipart/mixed header with your requests. Alternatively, for use with Server-Sent Events, the Content-Type: text/event-stream header should be passed. If requests use @defer but don't send one of these headers, requests will be treated as regular GraphQL requests.

You can learn more about Incremental Delivery and GraphQL over Server-Sent Events RFCs on GitHub.

In the example below we have a GraphQL query that fetches a user, but invokes @defer for the DetailedInfo fragment. Since fetching friends and posts can take longer due to the complex nested database requests, we can defer until it's ready!

query { user(id: "...") { name bio ...DetailedInfo @defer } } fragment DetailedInfo on User { friends { name bio } posts { title content } }

The request will return a response where hasNext is true if the deferred data is still processing:

{ "data": { "user": { "name": "...", "bio": "..." } }, "hasNext": true }

Grafbase will automatically update the event stream with the deferred data when available.

Outside Pathfinder, you will need to manage the merging of results yourself until GraphQL clients natively support @defer.

import { ExecutionResult } from 'graphql-sse' import merge from 'lodash.merge' import update from 'lodash.update' const mergeResults = ( result: ExecutionResult<Record<string, unknown>, unknown>, lastResult?: ExecutionResult<Record<string, unknown>, unknown>, ) => { if (!('path' in result) || lastResult === undefined) { return result } const path = result.path as string[] const combined = update(lastResult, ['data', ...path], value => merge(value, result.data), ) const errors = [...(lastResult?.errors ?? []), ...(result.errors ?? [])] return { ...combined, ...(errors.length > 0 ? { errors } : undefined) } }

We'd love to hear your feedback and ideas, so join us on Discord.