Use @defer and @stream with GraphQL Yoga. Stream and defer are directives that allow you to improve latency for clients by sending the most important data as soon as it's ready.
Stream and defer are directives that allow you to improve latency for clients by sending the most
important data as soon as it’s ready.
As applications grow, the GraphQL operation documents can get bigger. The server will only send the
response back once all the data requested in the query is ready. However, not all requested data is
of equal importance, and the client may not need all of the data at once. To remedy this, GraphQL
specification working group is working on
introducing new @defer and @stream directives
which allows applications to request a subset of data which is critical and get the rest of the data
in subsequent responses from the server. This
proposal is in
Stage 2, meaning
GraphQL libraries can start implementing this as experimental feature to provide feedback to the
working group.
Installation
Enabling support for the @defer and @stream directive requires installing a plugin.
npm i @graphql-yoga/plugin-defer-stream
yarn add @graphql-yoga/plugin-defer-stream
pnpm add @graphql-yoga/plugin-defer-stream
bun add @graphql-yoga/plugin-defer-stream
Quick Start
The following schema shows a schema whose field resolvers are slow
Slow Schema
import { createServer } from 'node:http'import { setTimeout as setTimeout$ } from 'node:timers/promises'import { createSchema, createYoga } from 'graphql-yoga'import { useDeferStream } from '@graphql-yoga/plugin-defer-stream'const typeDefs = /* GraphQL */ ` type Query { alphabet: [String!]! """ A field that resolves fast. """ fastField: String! """ A field that resolves slowly. Maybe you want to @defer this field ;) """ slowField(waitFor: Int! = 5000): String }`const resolvers = { Query: { async *alphabet() { for (const character of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) { yield character await setTimeout$(1000) } }, fastField: async () => { await setTimeout$(100) return 'I am speed' }, slowField: async (_, { waitFor }) => { await setTimeout$(waitFor) return 'I am slow' } }}const yoga = createYoga({ schema: createSchema({ typeDefs, resolvers }), plugins: [useDeferStream()]})const server = createServer(yoga)server.listen(4000, () => { console.info('Server is running on http://localhost:4000/graphql')})
Create this file and start the GraphQL server.
Using Defer
The @defer directive allows you to post-pone the delivery of one or more (slow) fields grouped in
an inlined or spread fragment.
AsyncGenerators
as declared via the async * keywords are prone to memory leaks and leaking timers. In the previous
examples they are used for simplicity.
For real-world usage we recommend using Repeater as a safe alternative.
import { createServer } from 'node:http'import { createSchema, createYoga, Repeater } from 'graphql-yoga'import { useDeferStream } from '@graphql-yoga/plugin-defer-stream'const yoga = createYoga({ schema: createSchema({ typeDefs: /* GraphQL */ ` type Query { alphabet: [String!]! } `, resolvers: { Query: { alphabet: () => new Repeater<string>(async (push, stop) => { const values = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] const publish = () => { const value = values.shift() console.log('publish', value) if (value) { push(value) } if (values.length === 0) { stop() } } let interval = setInterval(publish, 1000) publish() await stop.then(() => { console.log('cancel') clearInterval(interval) }) }) } } }), plugins: [useDeferStream()]})const server = createServer(yoga)server.listen(4000, () => { console.info('Server is running on http://localhost:4000/graphql')})
If you execute the following GraphQL operation via GraphiQL and click the stop button before the
last value has been sent to the client, no further values will be published as the underlying source
(Repeater) has been stopped and disposed. In contrast, just using a AsyncGenerator would continue
to run timers even after the client has stopped listening.
GraphQL Operation using @stream
query StreamAlphabet { alphabet @stream}
This site uses cookies for analytics and improving your experience.