Subscriptions and incremental delivery

GraphQL · Advanced · 12 min

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-ws library; the older subscriptions-transport-ws is 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
  • subscribe returns an async iterator; resolve maps 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.

Check yourself

  1. How many root fields may a subscription operation select?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: graphql.org/learn/subscriptions/