GraphQL Subscriptions
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate no automated tooling catches this — the code_pattern describes a polling loop around a GraphQL query, which requires a human reviewer or integration test to identify. No static analysis tool is listed; the misuse (polling instead of subscribing, or missing reconnection/auth) is only visible through code review or runtime observation.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix references replacing polling queries with `asyncIterator` on the server and `useSubscription` on the client — this spans at least two layers (server resolver setup, client hook replacement), plus the common_mistakes around authentication on WebSocket handshake and reconnection logic add further implementation surface across multiple concerns.
Closest to 'persistent productivity tax' (b5). Subscriptions require a persistent WebSocket transport layer distinct from HTTP, impacting server infrastructure (WebSocket server setup), client code (reconnection logic, auth token forwarding), and deployment configuration. This is a cross-cutting concern within the web context but does not reshape the entire system architecture, landing it between localised and strong gravitational pull.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The canonical misconception is that subscriptions work over HTTP like queries and mutations — a competent GraphQL developer familiar with queries/mutations will naturally assume the same HTTP transport applies, but subscriptions require a fundamentally different persistent-connection protocol (WebSockets). This contradicts the mental model established by the rest of GraphQL's operation types and has cascading misuse consequences (broken auth, no reconnection handling).
Also Known As
TL;DR
Explanation
GraphQL has three operation types: query (read), mutation (write), and subscription (real-time stream). Subscriptions maintain a persistent connection — typically over WebSockets (using the `graphql-ws` or legacy `subscriptions-transport-ws` protocol) or Server-Sent Events. When a defined event fires on the server (a message sent, a price updated, a job completed), the server pushes the new data through the subscription channel to all subscribed clients. The server must implement a pub/sub mechanism internally — Apollo Server uses Redis or in-memory event emitters. Each subscription field defines a `subscribe` resolver that returns an async iterator, and a `resolve` function that shapes the payload. Unlike polling, subscriptions are push-based, meaning clients receive updates immediately without periodic requests. PHP implementations typically use Ratchet, ReactPHP, or delegate subscriptions to a dedicated Node.js service.
Diagram
sequenceDiagram
participant C as Client
participant S as GraphQL Server
participant P as PubSub
C->>S: WebSocket connect + subscribe messageSent
S->>P: asyncIterator MESSAGE_SENT_ch1
Note over C,S: Connection held open
P->>S: Event fired - new message
S->>C: Push subscription data
P->>S: Event fired - another message
S->>C: Push subscription data
C->>S: Unsubscribe / disconnect
Common Misconception
Why It Matters
Common Mistakes
- Using subscriptions for data that changes infrequently — a polling query or cache invalidation is simpler and cheaper for low-frequency updates.
- Not handling reconnection on the client — WebSocket connections drop; clients must implement exponential backoff reconnect logic.
- Subscribing to overly broad events and filtering on the client — filter at the subscription resolver level to avoid pushing irrelevant data over the wire.
- Forgetting authentication on the WebSocket handshake — HTTP auth headers are not automatically forwarded; pass tokens in the WebSocket connection_init params.
Code Examples
// Client: polling every 2 seconds instead of subscribing
setInterval(async () => {
const { data } = await client.query({
query: GET_MESSAGES,
fetchPolicy: 'network-only',
});
setMessages(data.messages);
}, 2000); // hammers the server even when nothing changed
// Schema definition:
type Subscription {
messageSent(channelId: ID!): Message!
}
// Server resolver:
const resolvers = {
Subscription: {
messageSent: {
subscribe: (_, { channelId }, { pubsub }) =>
pubsub.asyncIterator(`MESSAGE_SENT_${channelId}`),
resolve: (payload) => payload.message,
},
},
};
// Client (React + Apollo):
const { data } = useSubscription(MESSAGE_SENT_SUBSCRIPTION, {
variables: { channelId },
onData: ({ data }) => addMessage(data.messageSent),
});