Subscriptions
Real-time Updates with GraphQL Subscriptions
Subscriptions enable real-time, event-based communication between server and client. Unlike queries and mutations, subscriptions maintain a persistent connection and push data when events occur.
What are Subscriptions?
Subscriptions allow clients to listen to real-time updates from the server. They're ideal for chat applications, live notifications, real-time dashboards, and collaborative editing.
type Subscription {
messageAdded(channelId: ID!): Message
userStatusChanged(userId: ID!): User
commentAdded(postId: ID!): Comment
notificationReceived: Notification
}
type Message {
id: ID!
content: String!
author: User!
channel: Channel!
createdAt: DateTime!
}
PubSub Pattern
GraphQL subscriptions typically use the Publish-Subscribe pattern. The server publishes events to topics, and subscribed clients receive updates.
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
// Mutation that publishes event
const resolvers = {
Mutation: {
sendMessage: async (parent, { channelId, content }, { user }) => {
const message = await Message.create({
channelId,
content,
authorId: user.id
});
// Publish event to subscribers
pubsub.publish('MESSAGE_ADDED', {
messageAdded: message,
channelId
});
return message;
}
},
Subscription: {
messageAdded: {
subscribe: (parent, { channelId }) => {
return pubsub.asyncIterator(['MESSAGE_ADDED']);
},
resolve: (payload, { channelId }) => {
// Filter by channel
if (payload.channelId === channelId) {
return payload.messageAdded;
}
return null;
}
}
}
};
Subscription Resolvers
Subscription resolvers have two functions: subscribe (sets up the subscription) and resolve (transforms the published data).
const resolvers = {
Subscription: {
commentAdded: {
// Setup subscription
subscribe: withFilter(
() => pubsub.asyncIterator(['COMMENT_ADDED']),
(payload, variables) => {
// Filter: only send if comment is for this post
return payload.commentAdded.postId === variables.postId;
}
),
// Transform payload
resolve: (payload) => {
return payload.commentAdded;
}
},
userStatusChanged: {
subscribe: withFilter(
() => pubsub.asyncIterator(['USER_STATUS_CHANGED']),
(payload, variables, context) => {
// Auth check
if (!context.user) return false;
// Only notify friends
return payload.userId === variables.userId &&
context.user.friends.includes(payload.userId);
}
)
}
}
};
WebSocket Transport
Subscriptions require WebSocket connections for bidirectional communication. Apollo Server uses the graphql-ws protocol.
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import express from 'express';
const app = express();
const httpServer = createServer(app);
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
// WebSocket server for subscriptions
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
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', express.json(), expressMiddleware(server));
httpServer.listen(4000);
Filtering Subscriptions
The withFilter function allows you to filter which subscribers receive which events.
import { withFilter } from 'graphql-subscriptions';
const resolvers = {
Subscription: {
// Filter by user ID
notificationReceived: {
subscribe: withFilter(
() => pubsub.asyncIterator(['NOTIFICATION']),
(payload, variables) => {
return payload.userId === variables.userId;
}
)
},
// Filter by multiple criteria
orderUpdated: {
subscribe: withFilter(
() => pubsub.asyncIterator(['ORDER_UPDATED']),
(payload, variables, context) => {
const { orderId, status } = variables;
const order = payload.orderUpdated;
// Filter by order ID
if (orderId && order.id !== orderId) return false;
// Filter by status
if (status && order.status !== status) return false;
// Auth: user must own the order
return order.userId === context.user.id;
}
)
}
}
};
Subscription Lifecycle
Understanding the subscription lifecycle helps debug and optimize real-time features.
const resolvers = {
Subscription: {
messageAdded: {
subscribe: async (parent, args, context) => {
// 1. Connection established
console.log('Subscriber connected:', context.user.id);
// 2. Validate permissions
const channel = await Channel.findById(args.channelId);
if (!channel.hasAccess(context.user)) {
throw new Error('Access denied');
}
// 3. Setup iterator
const iterator = pubsub.asyncIterator(['MESSAGE_ADDED']);
// 4. Cleanup on disconnect
const cleanup = () => {
console.log('Subscriber disconnected:', context.user.id);
// Cleanup resources
};
iterator.return = async () => {
cleanup();
return { value: undefined, done: true };
};
return iterator;
}
}
}
};
Client-Side Subscription (React)
import { useSubscription, gql } from '@apollo/client';
const MESSAGE_SUBSCRIPTION = gql`
subscription MessageAdded($channelId: ID!) {
messageAdded(channelId: $channelId) {
id
content
author {
id
name
}
createdAt
}
}
`;
function ChatRoom({ channelId }) {
const { data, loading, error } = useSubscription(
MESSAGE_SUBSCRIPTION,
{ variables: { channelId } }
);
if (loading) return <p>Connecting...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
{data && (
<div className="new-message">
<strong>{data.messageAdded.author.name}:</strong>
{data.messageAdded.content}
</div>
)}
</div>
);
}
- Implement a subscription for live comment updates on a blog post
- Add filtering to only send comments to users who have access to the post
- Create a mutation that publishes the comment event
- Build a React component that subscribes and displays new comments in real-time
- Add authentication to the subscription context