Subscriptions
GraphQL subscriptions let clients receive pushed updates from the server.
They are commonly implemented over WebSockets, although the exact transport depends on the server and client setup.
When Subscriptions Help
Use subscriptions when the server needs to notify the client quickly.
Good fits:
- chat messages
- live comments
- notifications
- collaborative editing
- job status updates
- presence updates
Avoid subscriptions when occasional polling is simpler and good enough.
Subscription Operation
subscription CommentAdded($lessonId: ID!) {
commentAdded(lessonId: $lessonId) {
id
body
author {
id
name
}
createdAt
}
}Subscriptions should be scoped. A user should subscribe only to data they are allowed to see.
Apollo Subscription Setup
Apollo commonly uses a split link: HTTP for queries and mutations, WebSocket for subscriptions.
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === "OperationDefinition" && definition.operation === "subscription";
},
wsLink,
httpLink
);The app still uses one ApolloClient, but operations travel through different transports.
Using a Subscription
const COMMENT_ADDED = gql`
subscription CommentAdded($lessonId: ID!) {
commentAdded(lessonId: $lessonId) {
id
body
__typename
}
}
`;
function LiveComments({ lessonId }) {
const { data } = useSubscription(COMMENT_ADDED, {
variables: { lessonId },
});
useEffect(() => {
if (data?.commentAdded) {
// update local state or Apollo cache
}
}, [data]);
return null;
}Many apps subscribe inside the component or provider that owns the relevant list.
Updating the Cache
Subscription payloads often need to be merged into the cache.
useSubscription(COMMENT_ADDED, {
variables: { lessonId },
onData({ client, data }) {
const comment = data.data?.commentAdded;
if (!comment) return;
client.cache.modify({
id: client.cache.identify({ __typename: "Lesson", id: lessonId }),
fields: {
comments(existingRefs = [], { readField }) {
const alreadyExists = existingRefs.some(
(ref) => readField("id", ref) === comment.id
);
if (alreadyExists) return existingRefs;
const newRef = client.cache.writeFragment({
data: comment,
fragment: gql`
fragment LiveComment on Comment {
id
body
__typename
}
`,
});
return [...existingRefs, newRef];
},
},
});
},
});Deduplicate by ID. Subscriptions can deliver events already reflected by an optimistic mutation or refetch.
Connection Auth
Subscription connections often need authentication.
The server must verify:
- who opened the connection
- which channels or topics they can subscribe to
- whether permissions changed during the connection
- whether the token expired
Do not assume that because a user loaded the page, every subscription event is allowed.
Reconnection and Missed Events
Subscriptions can disconnect.
After reconnecting, the client may need to refetch current state.
connected to comments
-> laptop sleeps
-> connection drops
-> new comments are created
-> reconnect
-> refetch comments or request missed eventsIf exact delivery matters, use sequence numbers, durable events, or a backend recovery API.
Common Mistakes
- Subscribing to broad global events instead of scoped data.
- Forgetting to update or deduplicate cache entries.
- Assuming every event is delivered exactly once.
- Ignoring auth changes after the socket connects.
- Opening duplicate subscriptions from repeated component mounts.
- Using subscriptions for data that polling could handle.
Edge Case
A user creates a comment with an optimistic mutation and then receives the same comment through a subscription.
Without deduplication, the UI may show it twice.
Why should subscription handlers often deduplicate by ID?
Practical Challenge
Add live comments to a lesson page.
Design:
- subscription operation
- Apollo link setup
- cache update behavior
- deduplication rule
- reconnect recovery
- authorization checks
- fallback polling plan if WebSockets are unavailable
Recap
GraphQL subscriptions bring realtime updates into the same typed API model as queries and mutations.
They still require WebSocket-style thinking: connection lifecycle, auth, missed events, deduplication, and careful cache updates.