GraphQL

Subscriptions And Real-Time Data

The Shape Of A Subscription

Subscriptions live on a root Subscription type and look syntactically like queries:

subscription OnReviewPosted($albumId: ID!) {
  reviewPosted(albumId: $albumId) {
    id
    rating
    body
    author {
      displayName
    }
  }
}

The difference is in the lifecycle. Executing this does not produce one response; it produces a stream. Every time someone posts a review for that album, the server runs the selection set against the new review and pushes one result document to the client. The stream continues until either side ends it.

One rule to know: a subscription operation must select exactly one root field. A query can ask for artist and search side by side; a subscription cannot subscribe to reviewPosted and trackPlayed in one operation, because each root field defines its own event stream. Open two subscriptions if you need two streams.

What Happens On The Server

A subscription field has two jobs where queries have one:

  1. Subscribe: map the field and its arguments onto an event source — a Redis pub/sub channel, a message broker topic, an in-process event emitter. The arguments typically select the channel (reviews.al_301).
  2. Resolve per event: when an event arrives, run resolvers for the selection set against the event payload, producing a normal data document to push.

This means the plumbing is your application's job: GraphQL specifies the operation semantics, but you supply the pub/sub backbone. In a PHP deployment this is a real architectural decision, because classic PHP-FPM request handling cannot hold thousands of open connections — long-lived subscription servers are usually built on an async runtime (Swoole, ReactPHP, AMPHP) or delegated to a dedicated service or hosted layer, while queries and mutations stay on ordinary FPM.

Transports

The GraphQL spec is silent about how the stream travels. In practice you will meet two transports:

  • WebSockets, using the graphql-ws protocol (a small message protocol layered on the socket: connection init, subscribe, next, complete). This is the most common choice and supports bidirectional traffic.
  • Server-Sent Events (SSE), where the client holds an HTTP response open and the server writes one chunk per event. Simpler infrastructure (plain HTTP, works with most proxies), one-directional, which is all subscriptions need.

Whichever transport you choose, plan for the boring failure modes: connections drop, so clients must reconnect with backoff; events fired while disconnected are missed unless you design a catch-up mechanism (for example, a query that fetches everything since a cursor after reconnecting).

When To Use Subscriptions — And When Not To

Subscriptions carry ongoing cost: every open subscription is server memory, a routing entry, and a connection that load balancers and proxies must keep alive. Use them when the event itself is the product:

  • live chat and comment threads,
  • presence ("3 people are listening now"),
  • dashboards where multi-second staleness is unacceptable,
  • collaborative editing signals.

Prefer simpler tools when they suffice:

  • Polling a query every 30–60 seconds is unbeatable for weakly-live data like follower counts. It reuses all your existing query infrastructure.
  • Refetch after mutation covers the common "my own write should appear immediately" case — the mutation payload already returns the new state; no live channel needed.

A useful rule: reach for subscriptions when other people's actions must appear on the user's screen within a couple of seconds. Your own actions come back on the mutation response, and slow-moving data can be polled.

Designing Subscription Events

Treat each subscription field as an event contract, and keep it small and specific (reviewPosted, not albumChanged with a type flag). Include enough in the payload for the client to update its cache — typically the affected entity with its id — rather than a bare notification that forces a follow-up query. And carry authorization through the whole lifetime: the check that ran when the subscription started may no longer hold an hour later, so re-check on delivery for anything sensitive (the authorization lesson returns to this).

What To Check

Before moving on, make sure you can:

  • describe how a subscription's lifecycle differs from a query's
  • state the single-root-field rule for subscription operations
  • explain the two server-side halves: mapping to an event source, and resolving per event
  • name the two common transports and one operational concern of each
  • explain why plain PHP-FPM is a poor host for subscription connections and what the alternatives are
  • decide between subscription, polling, and refetch-after-mutation for a given feature

What You Should Be Able To Do

After this lesson, you should be able to design the real-time surface of an API: pick which events deserve subscription fields, shape their payloads so clients can update state without extra round trips, choose a transport and reconnection story, and defend "just poll it" where a subscription would be over-engineering.