GraphQL Subscriptions
Queries and mutations are request-response. Subscriptions are GraphQL's third operation type, for real-time updates: the client subscribes to an event with a selection set, and the server pushes a result each time that event occurs, shaped exactly like a query response. Typical uses are chat messages, notifications, live order status, collaborative editing presence, and dashboards.
Subscriptions keep GraphQL's strengths (typed schema, client-chosen fields) but bring the operational concerns of real-time systems: long-lived connections, fan-out to many subscribers across multiple server instances, per-event authorization, and reconnection. Knowing those trade-offs helps you decide when subscriptions are worth it versus simpler polling.
TL;DR
- A
subscriptionoperation opens a long-lived stream; the server sends a payload per event. - Common transports:
graphql-wsover WebSockets, and GraphQL over Server-Sent Events (simpler, HTTP-native). - Resolvers return an async iterator fed by a pub/sub mechanism; each event is then resolved with the client's selection set.
- In production, use a shared pub/sub backend (Redis, NATS, Kafka) so events reach subscribers on any server instance.
- Authenticate the connection and authorize every event, filtering to what each subscriber may see.
- Subscriptions are for small, frequent, event-driven updates. For rarely changing data, polling or refetch-on-focus is often simpler.
Quick Example
Schema:
Server (GraphQL Yoga with a Redis-backed pub/sub):
Client (Apollo or urql with graphql-ws):
Core Concepts
How Subscriptions Execute
- The client sends a subscription operation over a persistent transport.
- The server validates it and calls the field's
subscribefunction, which returns an async iterator (a stream of events). - For each event, the server runs the normal execution on the selection set, with
resolvemapping the event to the field's value and nested resolvers filling in fields, then sends the result. - The stream ends when the client unsubscribes, the connection closes, or the server completes it.
A subscription operation must select exactly one root field.
Transports
SSE is often easier to operate: it uses plain HTTP, reconnects automatically, and passes through proxies and load balancers designed for HTTP. WebSockets suit high-frequency, bidirectional scenarios.
Pub/Sub and Horizontal Scaling
In-memory pub/sub only reaches subscribers connected to the same process. With several server instances behind a load balancer, a mutation handled by instance A must notify subscribers on instances B and C. Use a shared broker:
- Redis Pub/Sub: simple and fast; at-most-once delivery.
- NATS, Kafka, Google Pub/Sub: when you already run them, or need durability and replay.
- Postgres LISTEN/NOTIFY: fine for modest scale.
Publish domain events (from mutations, background jobs, or change data capture) and let the GraphQL layer subscribe to them. That keeps real-time delivery decoupled from business logic.
Filtering and Authorization
- Authenticate at connection time:
graphql-wssends aconnection_initpayload, where tokens are validated before any subscription starts. - Authorize at subscribe time: can this viewer subscribe to this order, room, or channel?
- Authorize and filter per event: permissions can change mid-connection, and events may contain data the viewer shouldn't see. Filter events and resolve fields with the viewer's context.
- Use targeted topics (
ORDER_STATUS_CHANGED:ord_812) to avoid broadcasting every event to every subscriber and filtering in memory.
Alternatives
GraphQL's own guidance: subscriptions are best for small incremental changes to large objects, or low-latency real-time updates. Otherwise, prefer polling.
Best Practices
Send Small Events, Let Clients Select Data
Publish a lightweight event (IDs plus what changed), and let the subscription's selection set resolve the fields each client needs, batched with DataLoader when many subscribers receive the same event.
Plan for Reconnection and Missed Events
Connections drop on mobile networks and deploys. With at-most-once pub/sub, events during a disconnect are lost. On reconnect, clients should refetch current state with a query, then resume the subscription. Use sequence numbers or timestamps if exact replay matters.
Limit Connections and Subscriptions
Cap subscriptions per connection and connections per user, and set idle timeouts and keep-alive pings. Every open subscription holds server memory. See GraphQL security.
Load-Balance Long-Lived Connections Carefully
Configure proxies for WebSocket upgrades or SSE (disable buffering, raise idle timeouts), and drain connections gracefully on deploy so clients reconnect smoothly rather than all at once.
Common Mistakes
In-Memory Pub/Sub in a Multi-Instance Deployment
Works locally with one process, then silently drops events in production behind a load balancer. Use Redis or another shared broker.
Broadcasting Everything and Filtering in Memory
Publishing every order change to one topic and filtering per subscriber in every instance wastes CPU at scale. Use targeted topics keyed by entity or tenant.
Skipping Per-Event Authorization
Checking permissions only when the subscription starts can leak data if the viewer's access is revoked, or if events include data from related objects the viewer can't see.
FAQ
Do GraphQL subscriptions require WebSockets?
No. graphql-ws over WebSockets is the most common transport, but GraphQL over Server-Sent Events and multipart HTTP responses are well-supported alternatives that work over ordinary HTTP and are often easier to operate.
How do subscriptions scale?
The GraphQL servers hold connections and execute selection sets; a shared pub/sub broker distributes events to every instance. Scale horizontally by adding server instances, use targeted topics to avoid wasted fan-out, and batch data loading for events delivered to many subscribers.
Should I use subscriptions or polling?
Polling is simpler and adequate for data that changes every few seconds or less often, or where slight staleness is fine. Use subscriptions when users need low-latency updates (chat, notifications, live tracking), or when polling would waste significant resources.
Can subscriptions work with federation?
Yes. Apollo Router and other federation gateways support subscriptions, routing each to the subgraph that owns the subscription field, over WebSockets or multipart HTTP. See GraphQL federation.
Related Topics
- GraphQL — The query language overview
- WebSockets — The most common subscription transport
- Server-Sent Events — An HTTP-native alternative
- Redis Pub/Sub & Streams — Scaling event distribution
- GraphQL Security — Authorizing long-lived connections
- Event-Driven Architecture — Publishing the domain events subscriptions deliver