The Slack Notification Problem
You are building a chat app. Users need to see new messages instantly, not after polling every 2 seconds, not after refreshing the page. Slack, Discord, and every real-time app solve this with server push. Real-Time GraphQL Subscriptions give you this power by allowing the server to push updates to clients instantly using the same query language you already know for fetching data.
The Three GraphQL Operations
GraphQL Operation Types:
query { # READ - one-time fetch
user(id: 42) { name, email }
}
mutation { # WRITE - change data
sendMessage(chatId: 'g-42', text: 'Hi') {
id, sentAt
}
}
subscription { # STREAM - real-time push
messageReceived(chatId: 'g-42') {
id
text
sender { name, avatar }
sentAt
}
}
Query: Client asks, server responds once.
Mutation: Client sends change, server responds once.
Subscription: Client subscribes, server pushes MANY times.How Subscriptions Work Under the Hood
Subscription Lifecycle:
[Client] [GraphQL Server]
| |
| 1. WS handshake (HTTP upgrade) |
|----------------------------------->|
| |
| 2. Subscribe: messageReceived |
|----------------------------------->|
| [registers subscription]
| [maps to event source]
| |
| ... time passes ... |
| |
| [User B sends a message]
| [event fires]
| [server evaluates query]
| |
| 3. Push: { sender: 'Bob', |
| text: 'Hey!' } |
|<-----------------------------------|
| |
| ... more messages ... |
| |
| 4. Unsubscribe or disconnect |
|----------------------------------->|
| [cleans up subscription]Real Code: Setting Up Subscriptions
# Server (Python + Strawberry GraphQL):
import strawberry
from strawberry.types import Info
import asyncio
from typing import AsyncGenerator
@strawberry.type
class Subscription:
@strawberry.subscription
async def message_received(
self, chat_id: str
) -> AsyncGenerator[Message, None]:
# Subscribe to Redis pub/sub channel
async for event in redis.subscribe(f'chat:{chat_id}'):
yield Message(
id=event['id'],
text=event['text'],
sender=event['sender'],
sent_at=event['sent_at']
)
# Client (JavaScript):
const subscription = graphql`
subscription OnMessage($chatId: String!) {
messageReceived(chatId: $chatId) {
id, text, sender { name, avatar }, sentAt
}
}
`;
// Client gets EXACTLY the fields it asked for - no more, no lessScaling Subscriptions Across Servers
Single server: simple
[Client] <--WS--> [Server with all subscriptions in memory]
Multi-server: needs pub/sub backbone
[Client A] [Client B] [Client C]
| | |
[Server 1] [Server 2] [Server 3]
| | |
+-------[Redis Pub/Sub]----------+
Problem: User A is on Server 1, User B on Server 2.
User B sends message -> mutation hits Server 2.
Server 2 publishes event to Redis channel.
Server 1 receives event from Redis.
Server 1 pushes to Client A's WebSocket.
Memory per subscription: ~1-5 KB
100K active subscribers: ~100-500 MB
Redis pub/sub latency: ~1-5 ms addedSubscriptions vs Polling vs SSE
Approach Latency Efficiency Complexity Best For
-------------- --------- ---------- ---------- ----------------
Polling 1-10s Low (waste) Simple Legacy clients
Long Polling ~instant Medium Medium Simple real-time
SSE ~instant High Medium One-way streams
WebSocket ~instant High High Bidirectional
GQL Sub (WS) ~instant High Medium Type-safe pushInterview Tip
When designing a real-time feature with a GraphQL API, say: 'I would use GraphQL subscriptions over WebSocket for push updates. The client subscribes using the same query language as reads, requesting exactly the fields it needs. For multi-server scaling, I would add Redis Pub/Sub as a backbone so events published on any server reach the correct subscriber. I would filter events server-side to avoid waking every subscription on every event - only subscribers interested in the specific chat room receive the push.'
Key Takeaway
GraphQL subscriptions provide real-time push updates using the same query language as the rest of your API. They run over WebSockets and need pub/sub infrastructure for multi-server deployment. Use them for chat, live feeds, and dashboards where polling is too slow.
