Graphql Subscriptions — Real-Time Guide with Examples
GraphQL subscriptions enable real-time data push from server to client over WebSocket, allowing clients to receive live updates when specific events occur.
What You'll Learn
You will learn how to implement GraphQL subscriptions using Apollo Server and Redis Pub/Sub, build a real-time threat alert feed, and handle authentication and reconnection.
Why Subscriptions Matter
Polling for changes wastes bandwidth and introduces latency. Subscriptions push updates instantly. DodaTech's Durga Antivirus Pro dashboard uses GraphQL subscriptions to display live threat alerts — when a new zero-day is detected, every connected dashboard updates within milliseconds.
Setting Up Apollo Server with Subscriptions
const { ApolloServer, gql } = require("apollo-server");
const { PubSub } = require("graphql-subscriptions");
const pubsub = new PubSub();
const THREAT_ADDED = "THREAT_ADDED";
const typeDefs = gql`
type Threat {
id: ID!
name: String!
severity: String!
detected_at: String!
mitre_id: String
}
type Query {
threats: [Threat!]!
}
type Mutation {
reportThreat(name: String!, severity: String!): Threat!
}
type Subscription {
threatAdded: Threat!
}
`;
const threats = [];
const resolvers = {
Query: { threats: () => threats },
Mutation: {
reportThreat: (_, { name, severity }) => {
const threat = {
id: String(threats.length + 1),
name,
severity,
detected_at: new Date().toISOString(),
};
threats.push(threat);
pubsub.publish(THREAT_ADDED, { threatAdded: threat });
return threat;
},
},
Subscription: {
threatAdded: {
subscribe: () => pubsub.asyncIterator([THREAT_ADDED]),
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: { path: "/subscriptions" },
});
server.listen(4000).then(() => console.log("Server ready on :4000"));
Expected output:
# Server starts with WebSocket endpoint at ws://localhost:4000/subscriptions
# GraphQL Playground available at http://localhost:4000
Client-Side Subscription
import { ApolloClient, InMemoryCache, gql } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
const link = new WebSocketLink({
uri: "ws://localhost:4000/subscriptions",
options: { reconnect: true },
});
const client = new ApolloClient({ link, cache: new InMemoryCache() });
// Subscribe to new threats
client.subscribe({
query: gql`
subscription OnThreatAdded {
threatAdded {
id
name
severity
detected_at
}
}
`,
}).subscribe({
next(data) {
console.log("New threat detected:", data.data.threatAdded);
// Durga dashboard: show alert banner
showAlert(data.data.threatAdded);
},
error(err) {
console.error("Subscription error:", err);
},
});
Expected console output:
New threat detected: {
id: "42",
name: "Novel Ransomware Variant",
severity: "critical",
detected_at: "2026-06-24T14:30:00Z"
}
Filtered Subscriptions with Arguments
const typeDefs = gql`
type Subscription {
threatAdded(severity: String): Threat!
}
`;
const resolvers = {
Subscription: {
threatAdded: {
subscribe: (_, { severity }) => {
const iterator = pubsub.asyncIterator([THREAT_ADDED]);
// Filter: only yield threats matching the requested severity
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
while (true) {
const { value, done } = await iterator.next();
if (done) return { done: true };
const threat = value.threatAdded;
if (!severity || threat.severity === severity) {
return { value, done: false };
}
}
},
};
},
},
},
};
Expected client behavior:
# Client subscribes only to critical threats
subscription {
threatAdded(severity: "critical") {
name
severity
}
}
# Server pushes:
# {"data": {"threatAdded": {"name": "Log4j Exploit", "severity": "critical"}}
# (medium and low threats are filtered out)
Redis Pub/Sub for Multi-Process
const { RedisPubSub } = require("graphql-redis-subscriptions");
const Redis = require("ioredis");
const options = {
host: "localhost",
port: 6379,
};
const pubsub = new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
});
// Works across multiple server instances
// When one instance publishes, all instances receive the update
flowchart LR
C1["Client A\nDashboard"] --> WS1["WebSocket"]
C2["Client B\nDashboard"] --> WS2["WebSocket"]
WS1 --> AS["Apollo Server\nInstance 1"]
WS2 --> AS2["Apollo Server\nInstance 2"]
AS --> RP["Redis Pub/Sub"]
AS2 --> RP
M["Mutation:\nreportThreat"] --> AS
M --> AS2
RP -->|Broadcast| AS
RP -->|Broadcast| AS2
style RP fill:#dbeafe,stroke:#2563eb
style AS fill:#bbf7d0,stroke:#16a34a
style AS2 fill:#bbf7d0,stroke:#16a34a
style C1 fill:#fef3c7,stroke:#d97706
style C2 fill:#fef3c7,stroke:#d97706
Authentication with Subscriptions
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
onConnect: (connectionParams) => {
const token = connectionParams.authToken;
if (!token) throw new Error("Auth token required");
try {
const user = JWT.verify(token, SECRET_KEY);
return { user };
} catch {
throw new Error("Invalid token");
}
},
},
});
// Client passes auth token on connection
const link = new WebSocketLink({
uri: "ws://localhost:4000/subscriptions",
options: {
connectionParams: { authToken: "eyJhbGciOiJIUzI1NiIs..." },
},
});
Common Errors
1. Not Handling Reconnection
WebSocket connections drop. Without reconnect: true on the client, subscriptions stop working after a transient network issue. Apollo Client's WebSocketLink supports automatic reconnection.
2. Forgetting to Close Subscriptions
Unsubscribed listeners accumulate. Always call unsubscribe() when a component unmounts or when the user navigates away from the dashboard.
3. Scaling Without Redis Pub/Sub
With multiple server instances, a subscription connected to instance 1 does not receive mutations sent to instance 2. Use Redis Pub/Sub to broadcast across instances.
4. Subscribing Without Auth
Anyone can connect to the WebSocket endpoint and subscribe to all events if auth is not enforced. Validate tokens in the onConnect hook.
5. Subscription Payload Too Large
Sending full objects in every subscription update wastes bandwidth. Send minimal payloads with IDs and let clients fetch full details via query if needed.
Practice Questions
1. What transport protocol do GraphQL subscriptions typically use?
WebSocket (ws:// or wss://) over a single persistent TCP connection.
2. How do you filter subscription events on the server side?
Use an async Iterator pattern that skips events not matching the filter criteria before yielding them to the client.
3. Why is Redis Pub/Sub needed for subscriptions in production?
Because multiple server instances need to broadcast events to all connected clients, not just those connected to the instance that received the mutation.
4. Challenge: Implement a subscription that alerts when threat severity changes for a specific threat ID.
Use two events (THREAT_CREATED, THREAT_UPDATED), filter on threatId argument, and yield the updated threat when severity changes.
Mini Project: Real-Time Threat Dashboard
Build an Apollo Server with a threatAlerted subscription that pushes critical threat alerts to DodaTech dashboards. Include Redis Pub/Sub for horizontal scaling, JWT auth on the WebSocket connection, and a React client that displays alerts in real time.
Related Tutorials
GraphQL — WebSocket — RESTful API Design
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro