GraphQL subscriptions push data to clients over a persistent WebSocket connection. When a mutation triggers an event, every subscribed client receives the update instantly.
Server Setup
Install the WebSocket transport:
npm install graphql-ws ws @graphql-tools/schemaUpdate your server to handle both HTTP and WebSocket connections:
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const typeDefs = `#graphql
type Message {
id: ID!
text: String!
sender: String!
createdAt: String!
}
type Query {
messages: [Message!]!
}
type Mutation {
sendMessage(text: String!, sender: String!): Message!
}
type Subscription {
messageSent: Message!
}
`;
const messages = [];
let nextId = 1;
const resolvers = {
Query: {
messages: () => messages,
},
Mutation: {
sendMessage: (_, { text, sender }) => {
const message = {
id: String(nextId++),
text,
sender,
createdAt: new Date().toISOString(),
};
messages.push(message);
pubsub.publish('MESSAGE_SENT', { messageSent: message });
return message;
},
},
Subscription: {
messageSent: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_SENT']),
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const httpServer = createServer(app);
const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' });
useServer({ schema }, wsServer);Client Subscription
import { useSubscription, gql } from '@apollo/client';
const MESSAGE_SENT = gql`
subscription OnMessageSent {
messageSent {
id text sender createdAt
}
}
`;
function ChatMessages() {
const [messages, setMessages] = useState([]);
useSubscription(MESSAGE_SENT, {
onData: ({ data }) => {
setMessages(prev => [...prev, data.data.messageSent]);
},
});
return (
<ul>
{messages.map(msg => (
<li key={msg.id}>
<strong>{msg.sender}:</strong> {msg.text}
</li>
))}
</ul>
);
}Simulating Real-Time in the Browser
Ctrl+Enter
HTML
CSS
JS
Preview
PubSub in Production
The built-in PubSub from graphql-subscriptions is in-memory — it doesn't work across multiple server instances. For production, replace it with a Redis-backed PubSub:
npm install graphql-redis-subscriptions ioredisimport { RedisPubSub } from 'graphql-redis-subscriptions';
const pubsub = new RedisPubSub({
connection: { host: 'localhost', port: 6379 }
});Now publish and subscribe work across any number of server processes.
When to Use Subscriptions
| Use case | Recommendation |
|---|---|
| Chat messages | Subscription ✓ |
| Live notifications | Subscription ✓ |
| Dashboard that refreshes every 30s | Polling is simpler |
| Collaborative editing | Subscription ✓ |
| Long-running job status | Subscription or polling |
Subscriptions add complexity (WebSocket infrastructure, connection management). Use them when truly real-time behaviour is required.