Chat, notifications and live dashboards need server-pushed updates.
A subscription is a long-lived operation: the client subscribes once and receives a stream of results, one per event. Each subscription operation must have exactly one root field.
subscription OnMessage($room: ID!) { messageAdded(room: $room) { id text author { name } } }Server side, a subscription field has a subscribe function returning an async iterator of events, and an optional resolve that maps each event to the field value. For every event, the selection set is executed like a normal query, so nested resolvers and loaders work as usual.
Transport is not in the core spec. The common choices:
- graphql-ws protocol over WebSocket (the
graphql-wslibrary; the oldersubscriptions-transport-wsis deprecated). - Server-sent events (graphql-sse), simpler through proxies and HTTP/2.
In production, events usually come from a pub/sub system (Redis, Kafka, Postgres LISTEN/NOTIFY) so that any server instance can deliver to its connected clients. Authenticate during the connection handshake, not per message, and re-check permissions on sensitive events.
Related but different: the @defer and @stream directives (experimental in graphql-js 17 via experimentalExecuteIncrementally, and supported by some servers) send one query's result in increments so slow fields arrive later. Use subscriptions for events, polling for data that changes rarely.
Example
import { makeExecutableSchema } from "@graphql-tools/schema";
import { parse, subscribe } from "graphql";
async function* ticks() { for (let i = 1; i <= 3; i++) yield { tick: i }; }
const schema = makeExecutableSchema({
typeDefs: `type Query { ok: Boolean } type Subscription { tick: Int! }`,
resolvers: { Subscription: { tick: { subscribe: () => ticks() } } },
});
const stream = await subscribe({ schema, document: parse("subscription { tick }") });
for await (const result of stream) console.log(JSON.stringify(result.data));
// {"tick":1}
// {"tick":2}
// {"tick":3}Remember
- Subscriptions stream one result per event
subscribereturns an async iterator;resolvemaps events- Use graphql-ws or SSE; fan out events via pub/sub
Watch out for
- Keeping events in an in-memory emitter, which breaks as soon as you run two server instances.