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.