GraphQL subscriptions enable real-time data streaming from server to client. They're essential for building collaborative applications, live dashboards, and any feature requiring instant updates.
Understanding Subscriptions
What Are Subscriptions?
Subscriptions are long-lived operations that push data to clients when specific events occur. Unlike queries and mutations, subscriptions maintain an open connection for continuous updates.
Subscription vs REST
REST - Polling:
javascript
// Poll every 5 seconds
setInterval(async () => {
const data = await fetch('/api/messages');
updateMessages(data);
}, 5000);GraphQL - Real-time:
graphql
subscription {
newMessages {
id
content
author
timestamp
}
}WebSocket Setup
Server Setup with Apollo Server
javascript
import { ApolloServer } from '@apollo/server';
import { WebSocketServer } from 'ws';
import { useServer } from '@graphql-ws/lib/use/ws';
import { createServer } from 'http';
const typeDefs = `#graphql
type Message {
id: ID!
content: String!
author: String!
timestamp: DateTime!
}
type Query {
messages: [Message!]
}
type Mutation {
postMessage(content: String!): Message!
}
type Subscription {
newMessage: Message!
}
`;
const resolvers = {
Query: {
messages: () => messages
},
Mutation: {
postMessage: (_, { content }, { pubsub }) => {
const message = {
id: Date.now().toString(),
content,
author: currentUser,
timestamp: new Date().toISOString()
};
messages.push(message);
pubsub.publish('NEW_MESSAGE', { newMessage: message });
return message;
}
},
Subscription: {
newMessage: {
subscribe: (_, __, { pubsub }) => {
return pubsub.asyncIterator(['NEW_MESSAGE']);
}
}
}
};Express Server with WebSocket
javascript
import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import http from 'http';
const app = express();
const httpServer = http.createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const schema = makeExecutableSchema({ typeDefs, resolvers });
const serverCleanup = useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
}
};
}
}
]
});
await server.start();
app.use('/graphql', expressMiddleware(server));
const PORT = 4000;
httpServer.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}/graphql`);
});Client-Side Subscriptions
Apollo Client Setup
javascript
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { createClient } from 'graphql-ws';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/graphql',
connectionParams: {
authToken: userToken
}
}));
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache()
});Using Subscriptions in React
javascript
import { gql, useSubscription } from '@apollo/client';
const NEW_MESSAGE_SUBSCRIPTION = gql`
subscription OnNewMessage {
newMessage {
id
content
author
timestamp
}
}
`;
function MessageList() {
const { data, loading, error } = useSubscription(
NEW_MESSAGE_SUBSCRIPTION
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<Message message={data.newMessage} />
</div>
);
}Subscribing to Multiple Events
javascript
const MESSAGES_SUBSCRIPTION = gql`
subscription OnMessagesUpdates($channel: String!) {
messageUpdates(channel: $channel) {
id
content
type
timestamp
}
}
`;
function Channel({ channelId }) {
const { data, error } = useSubscription(
MESSAGES_SUBSCRIPTION,
{ variables: { channel: channelId } }
);
useEffect(() => {
if (data) {
if (data.messageUpdates.type === 'NEW') {
addMessage(data.messageUpdates);
} else if (data.messageUpdates.type === 'EDIT') {
updateMessage(data.messageUpdates);
}
}
}, [data]);
}Advanced Subscription Patterns
Filtering Subscriptions
graphql
type Subscription {
messageUpdates(roomId: ID!): Message!
}
const resolvers = {
Subscription: {
messageUpdates: {
subscribe: withFilter(
(_, { roomId }, { pubsub }) => {
return pubsub.asyncIterator([`MESSAGE_${roomId}`]);
},
(payload, variables) => {
return payload.messageUpdates.roomId === variables.roomId;
}
)
}
}
};Authenticated Subscriptions
javascript
const onConnect = async (ctx) => {
const token = ctx.connectionParams.authToken;
try {
const user = await verifyToken(token);
return { user };
} catch (err) {
throw new Error('Authentication failed');
}
};
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
onConnect
});Pagination with Subscriptions
javascript
const usePaginatedSubscription = (query) => {
const [items, setItems] = useState([]);
const [cursor, setCursor] = useState(null);
const { data } = useSubscription(query, {
variables: { cursor }
});
useEffect(() => {
if (data?.items) {
setItems(prev => [...prev, ...data.items]);
setCursor(data.cursor);
}
}, [data]);
return { items, loadMore: () => setCursor(cursor) };
};Subscription Best Practices
Connection Management
javascript
function SubscriptionComponent() {
const client = useApolloClient();
useEffect(() => {
const subscription = client.subscribe({
query: NEW_MESSAGE_SUBSCRIPTION
}).subscribe({
next: (data) => console.log('New message', data),
error: (err) => console.error('Subscription error', err)
});
return () => {
subscription.unsubscribe();
};
}, []);
}Error Handling
javascript
const { data, error } = useSubscription(
NEW_MESSAGE_SUBSCRIPTION,
{
onError: (error) => {
console.error('Subscription error:', error);
// Implement reconnection logic
reconnect();
}
}
);Performance Optimization
javascript
// Debounce rapid updates
import { debounce } from 'lodash';
const debouncedUpdate = debounce((data) => {
updateUI(data);
}, 300);
useSubscription(UPDATE_SUBSCRIPTION, {
onData: ({ data }) => {
debouncedUpdate(data);
}
});Common Use Cases
Real-Time Chat
javascript
const CHAT_SUBSCRIPTION = gql`
subscription OnMessage($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
content
sender {
id
name
avatar
}
createdAt
}
}
`;Live Dashboard
javascript
const METRICS_SUBSCRIPTION = gql`
subscription OnMetricsUpdate {
metricsUpdated {
cpuUsage
memoryUsage
activeUsers
requestsPerSecond
}
}
`;Collaborative Editing
javascript
const DOCUMENT_SUBSCRIPTION = gql`
subscription OnDocumentChange($documentId: ID!) {
documentChanged(documentId: $documentId) {
id
content
version
editedBy {
id
name
}
timestamp
}
}
`;Conclusion
GraphQL subscriptions enable powerful real-time features. Master them, and you'll build engaging, collaborative applications that keep users connected and informed.