Subscriptions
GraphQL Subscriptions allow clients to maintain an active connection to the server (usually via WebSockets) to receive real-time updates when specific events occur (e.g., a new message is posted in a chat room).
Overview
While Queries (Read) and Mutations (Write) operate over standard HTTP, Subscriptions require a persistent connection.
In NestJS, subscriptions are powered by the graphql-ws or subscriptions-transport-ws packages. You use the PubSub (Publish/Subscribe) pattern. When a mutation occurs (e.g., addComment), the server “publishes” an event. Any clients who have “subscribed” to that event receive the new comment data instantly.
Key Concepts
- WebSockets: The underlying protocol used for subscriptions.
PubSub: An event emitter used to publish and listen for events. (Note: The default in-memoryPubSubis for development only. Production requires Redis).@Subscription(): The decorator used to expose a subscription endpoint.filter: A function to determine if a specific client should receive a specific published event (e.g., only send the comment if it belongs to the post the client is viewing).
Code Examples
1. Enabling Subscriptions
You must explicitly enable subscriptions in your GraphQLModule configuration.
// app.module.ts
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql',
// Enable WebSocket subscriptions
subscriptions: {
'graphql-ws': true // Modern standard
},
}),
],
})
export class AppModule {}
2. Creating the PubSub Instance
For this example, we use the basic graphql-subscriptions package. In a real app, this should be provided as a global singleton via a custom module.
// pubsub.module.ts
import { Global, Module } from '@nestjs/common';
import { PubSub } from 'graphql-subscriptions';
@Global()
@Module({
providers: [
{
provide: 'PUB_SUB',
useValue: new PubSub(),
},
],
exports: ['PUB_SUB'],
})
export class PubSubModule {}
3. Publishing and Subscribing in the Resolver
When a mutation happens, we publish. When a client subscribes, we return an AsyncIterator.
import { Resolver, Mutation, Subscription, Args, Int } from '@nestjs/graphql';
import { Inject } from '@nestjs/common';
import { PubSub } from 'graphql-subscriptions';
import { Comment } from './comment.model';
const COMMENT_ADDED_EVENT = 'commentAdded';
@Resolver(() => Comment)
export class CommentsResolver {
// Inject the global PubSub instance
constructor(@Inject('PUB_SUB') private pubSub: PubSub) {}
// 1. The Mutation publishes the event
@Mutation(() => Comment)
async addComment(
@Args('postId', { type: () => Int }) postId: number,
@Args('text') text: string,
) {
const newComment = { id: Date.now(), postId, text };
// PUBLISH! The payload object MUST have a key matching the event name.
this.pubSub.publish(COMMENT_ADDED_EVENT, { commentAdded: newComment });
return newComment;
}
// 2. The Subscription listens for the event
@Subscription(() => Comment, {
// 3. Filter: Only notify the client if the comment belongs to the post they are watching!
filter: (payload, variables) => {
return payload.commentAdded.postId === variables.postId;
},
})
commentAdded(@Args('postId', { type: () => Int }) postId: number) {
// Returns an AsyncIterator that pushes data to the client's WebSocket
return this.pubSub.asyncIterator(COMMENT_ADDED_EVENT);
}
}
Best Practices
- Do not use in-memory PubSub in Production: The
new PubSub()provided bygraphql-subscriptionslives only in the memory of the current Node process. If you deploy your app across multiple servers (or Kubernetes pods), a mutation on Server A will not trigger subscriptions connected to Server B. You must use a Redis-backed PubSub (like@nestjs/microservicesRedis client orgraphql-redis-subscriptions) in production. - Authentication: WebSocket authentication is entirely different from HTTP authentication. You cannot rely on standard HTTP Headers (like Bearer tokens) because WebSockets don’t send them after the initial handshake. You must configure the
connectionParamsin the GraphQLModule to extract tokens during the WebSocket connection phase.