Working with Subscriptions
1. Defining Subscription Fields
| Rule | Detail |
|---|---|
| One root field | Operation must select exactly one |
| Resolver returns AsyncIterator | Yields events over time |
| Naming | Past-tense or noun: messageAdded, orderUpdated |
2. Setting Up Subscription Server
Example: graphql-ws + Apollo Server
import { useServer } from "graphql-ws/lib/use/ws";
import { WebSocketServer } from "ws";
const wsServer = new WebSocketServer({ server: httpServer, path: "/graphql" });
useServer({ schema, context: ctxFn }, wsServer);
| Transport | Library |
|---|---|
| graphql-ws | Modern, recommended |
| subscriptions-transport-ws | DEPRECATED |
| SSE | graphql-sse |
3. Subscribing to Events
Example: Resolver with PubSub
import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();
const resolvers = {
Subscription: {
messageAdded: {
subscribe: (_, { channelId }) => pubsub.asyncIterableIterator(["MESSAGE_ADDED:" + channelId])
}
}
};
4. Publishing Events
Example: Publish from mutation
Mutation: {
postMessage: async (_, { channelId, body }, { db }) => {
const message = await db.message.create({ data: { channelId, body } });
pubsub.publish("MESSAGE_ADDED:" + channelId, { messageAdded: message });
return message;
}
}
5. Filtering Subscription Events
Example: withFilter
import { withFilter } from "graphql-subscriptions";
Subscription: {
messageAdded: {
subscribe: withFilter(
() => pubsub.asyncIterableIterator(["MESSAGE_ADDED"]),
(payload, vars, ctx) => payload.messageAdded.channelId === vars.channelId && ctx.user.canRead(payload)
)
}
}
6. Using Subscription Variables
| Pattern | Use |
|---|---|
| Topic key | Subscribe to specific room/channel |
| Filter args | Server-side filter via withFilter |
| Auth claims | From connection context |
7. Handling Subscription Lifecycle
| Event | Handler |
|---|---|
| connectionInit | Auth handshake, build context |
| subscribe | Validate operation, return iterator |
| complete / disconnect | Cleanup resources, unsubscribe |
| ping/pong | Keepalive for proxies |
8. Implementing Real-time Updates
| Use case | Pattern |
|---|---|
| Chat | messageAdded(channelId) |
| Live counters | countChanged(metricId) |
| Notifications | notificationReceived(userId) |
| Live queries | Mercurius live queries / Apollo client polling fallback |
9. Using Subscription Resolvers
| Field | Purpose |
|---|---|
| subscribe | Returns AsyncIterator (required) |
| resolve | Optional: transform payload before sending |
10. Handling Subscription Errors
| Source | Strategy |
|---|---|
| Auth fail | Reject in connectionInit |
| Resolver throw | Sent as error message; subscription can continue |
| Connection drop | Client should reconnect with backoff |
11. Securing Subscriptions
| Layer | Mechanism |
|---|---|
| connectionParams | Send JWT in init payload |
| Origin check | Verify Origin header |
| Per-event auth | Re-check in withFilter |
| Rate limit | Cap connections + events per user |
| TLS | wss:// only in production |