← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

GraphQL Subscriptions

api_design Advanced
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

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.

e5 Effort Remediation debt — work required to fix once spotted

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.

b5 Burden Structural debt — long-term weight of choosing wrong

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.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

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).

About DEBT scoring →

Also Known As

GraphQL real-time GraphQL WebSocket GraphQL events subscription operation

TL;DR

A GraphQL operation type that opens a long-lived connection to the server and pushes real-time data updates to the client whenever a specific event occurs.

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

GraphQL subscriptions work over HTTP like queries and mutations — they require a persistent connection protocol (usually WebSockets). HTTP is a request/response protocol and cannot push data to a client without the client polling or using SSE. Subscriptions need a different transport layer.

Why It Matters

Polling is wasteful — it creates load regardless of whether data changed. Subscriptions push data only when events occur, reducing server load and delivering updates in real time. They are the right tool for chat, live dashboards, notifications, and collaborative editing features.

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

💡 Note
The server-side `asyncIterator` keyed by `channelId` ensures only subscribers for that channel receive the event — server-side filtering, not client-side.
✗ Vulnerable
// 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
✓ Fixed
// 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),
});

Added 24 Mar 2026
Views 34
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 2 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Amazonbot 15 Perplexity 4 Google 3 Ahrefs 3 Unknown AI 2 ChatGPT 1 Meta AI 1
crawler 28 crawler_json 1
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: High
⚡ Quick Fix
Replace high-frequency polling queries with a subscription using `asyncIterator` on the server and `useSubscription` on the client
📦 Applies To
web Laravel Symfony
🔗 Prerequisites
🔍 Detection Hints
setInterval or polling loop around a GraphQL query for real-time data; fetchPolicy: 'network-only' inside a timer
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Regenerate

✓ schema.org compliant