When GraphQL Subscriptions Make Sense
GraphQL has three operation types:
-
query— fetch data -
mutation— change data -
subscription— receive updates in real-time
Subscriptions make sense when you already have a GraphQL API and need real-time features without adding a separate WebSocket layer.
Server Setup with GraphQL Yoga
npm install graphql-yoga graphql
import { createYoga, createSchema } from 'graphql-yoga';
import { createPubSub } from '@graphql-yoga/subscription';
import { createServer } from 'http';
// Type-safe pub/sub
const pubSub = createPubSub<{
MESSAGE_CREATED: [channelId: string, payload: { message: Message }];
USER_TYPING: [channelId: string, payload: { userId: string }];
}>();
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Message {
id: ID!
content: String!
userId: String!
createdAt: String!
}
type Query {
messages(channelId: ID!): [Message!]!
}
type Mutation {
sendMessage(channelId: ID!, content: String!): Message!
}
type Subscription {
messageCreated(channelId: ID!): Message!
userTyping(channelId: ID!): String!
}
`,
resolvers: {
Query: {
messages: (_, { channelId }) => db.messages.findMany({ where: { channelId } }),
},
Mutation: {
sendMessage: async (_, { channelId, content }, { userId }) => {
const message = await db.messages.create({
data: { channelId, content, userId },
});
// Publish to subscribers
pubSub.publish('MESSAGE_CREATED', channelId, { message });
return message;
},
},
Subscription: {
messageCreated: {
// Return async iterator
subscribe: (_, { channelId }) =>
pubSub.subscribe('MESSAGE_CREATED', channelId),
// Transform the event payload
resolve: (payload) => payload.message,
},
userTyping: {
subscribe: (_, { channelId }) =>
pubSub.subscribe('USER_TYPING', channelId),
resolve: (payload) => payload.userId,
},
},
},
});
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(4000);
Client with Apollo
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
// HTTP for queries/mutations
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// WebSocket for subscriptions
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/graphql',
}));
// Route by operation type
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() });
// React component
import { useSubscription, gql } from '@apollo/client';
const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageCreated($channelId: ID!) {
messageCreated(channelId: $channelId) {
id
content
userId
createdAt
}
}
`;
function MessageList({ channelId }: { channelId: string }) {
const [messages, setMessages] = useState<Message[]>([]);
useSubscription(MESSAGE_SUBSCRIPTION, {
variables: { channelId },
onData: ({ data }) => {
if (data.data?.messageCreated) {
setMessages(prev => [...prev, data.data.messageCreated]);
}
},
});
return (
<ul>
{messages.map(msg => (
<li key={msg.id}>{msg.content}</li>
))}
</ul>
);
}
Scaling Subscriptions
In-process pub/sub doesn't work with multiple server instances:
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';
const pubSub = new RedisPubSub({
publisher: new Redis(process.env.REDIS_URL),
subscriber: new Redis(process.env.REDIS_URL),
});
// Now subscriptions work across all server instances
pubSub.publish('MESSAGE_CREATED', { channelId, message });
When NOT to Use GraphQL Subscriptions
- Simple use case: SSE is easier and lighter
- Non-GraphQL API: Don't add GraphQL just for subscriptions
- Very high frequency updates: WebSocket with binary protocol (like Socket.io rooms) is more efficient
If you're already on GraphQL and need real-time: subscriptions are the right tool. If you're starting fresh: evaluate SSE first.
GraphQL API with queries, mutations, and subscriptions: Whoff Agents AI SaaS Starter Kit includes both REST and GraphQL options.
Top comments (0)