GraphQL Subscriptions: WebSocket & Server-Sent Events Guide
GraphQL subscriptions enable real-time data push from server to client. This guide covers when to use subscriptions, how to implement WebSocket and SSE transports, and how to scale across multiple server instances using Redis pub/sub.
Subscription Schema & Resolver
Define subscriptions in the SDL and implement resolvers using AsyncIterator. The PubSub instance manages publish/subscribe internally:
import { PubSub } from 'graphql-subscriptions';
import { gql } from 'graphql-tag';
const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';
const typeDefs = gql`
type Message {
id: ID!
content: String!
author: String!
createdAt: String!
}
type Query { messages(channelId: ID!): [Message!]! }
type Mutation {
sendMessage(channelId: ID!, content: String!, author: String!): Message!
}
type Subscription {
messageAdded(channelId: ID!): Message!
}
`;
const resolvers = {
Mutation: {
sendMessage: async (_: unknown, args: any) => {
const message = await db.messages.create(args);
// Publish to all subscribers of this channel
await pubsub.publish(`${MESSAGE_ADDED}.${args.channelId}`, {
messageAdded: message,
});
return message;
},
},
Subscription: {
messageAdded: {
// AsyncIterator yields events when published
subscribe: (_: unknown, { channelId }: { channelId: string }) =>
pubsub.asyncIterator(`${MESSAGE_ADDED}.${channelId}`),
},
},
}; Apollo Server with graphql-ws (WebSocket)
Apollo Server 4 requires a separate WebSocket server for subscriptions. Use graphql-ws to handle the WebSocket handshake alongside your HTTP server:
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { useServer } from 'graphql-ws/lib/use/ws';
import { WebSocketServer } from 'ws';
import express from 'express';
import http from 'http';
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = express();
const httpServer = http.createServer(app);
// WebSocket server โ handles subscriptions
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
const serverCleanup = useServer({
schema,
onConnect: async (ctx) => {
// Authenticate during WS handshake
const token = ctx.connectionParams?.authToken as string;
if (!token) throw new Error('Missing auth token');
return { user: await validateToken(token) };
},
}, wsServer);
// HTTP server โ handles queries & mutations
const apolloServer = new ApolloServer({
schema,
plugins: [
{ async serverWillStart() {
return { async drainServer() { await serverCleanup.dispose(); } };
}},
],
});
await apolloServer.start();
app.use('/graphql', expressMiddleware(apolloServer));
httpServer.listen(4000); Scaling with Redis Pub/Sub
Replace the in-memory PubSub with graphql-redis-subscriptions to broadcast events across all server instances:
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';
// Two Redis clients: one for publish, one for subscribe
const pubsub = new RedisPubSub({
publisher: new Redis({ host: 'redis', port: 6379 }),
subscriber: new Redis({ host: 'redis', port: 6379 }),
});
// Drop-in replacement for the in-memory PubSub
// All server instances share the same Redis channel
// Instance A publishes โ Redis broadcasts โ Instance B/C push to their WS clients
// Use pattern subscriptions for dynamic channels
const asyncIterator = pubsub.asyncIterator(
`MESSAGE_ADDED.${channelId}` // per-channel topic
);
// With graphql-subscriptions-filter for server-side filtering:
import { withFilter } from 'graphql-subscriptions';
const resolvers = {
Subscription: {
messageAdded: {
subscribe: withFilter(
(_: any, { channelId }: any) => pubsub.asyncIterator('MESSAGE_ADDED'),
(payload: any, variables: any) => payload.messageAdded.channelId === variables.channelId,
),
},
},
}; WebSocket vs SSE vs Polling for Subscriptions
| Feature | WebSocket | Server-Sent Events | Polling |
|---|---|---|---|
| Direction | Full-duplex | Server โ Client only | Client-initiated |
| Protocol | ws:// / wss:// | HTTP (text/event-stream) | HTTP GET/POST |
| Proxy friendly | Sometimes tricky | Works everywhere | Works everywhere |
| Auto-reconnect | Manual | Built-in browser | N/A |
| Latency | Real-time | Real-time | Interval-based |
| Best for | Chat, collaborative | Notifications, feeds | Low-frequency updates |
Related Products
on GumroadAPI Testing Cheatsheet Pack
Quick-reference cheatsheets for REST, GraphQL, and WebSocket testing. Includes Postman collections, shell scripts for smoke tests and load tests, and JWT decoder.
Buy on Gumroad โDevPlaybook Interview Vault
210+ real technical interview questions with model answers, asked at Google, Meta, Amazon, Microsoft, Stripe, and Shopify. Covers Frontend (55), Backend (55), System Design (50), and DSA (50). Available in English and Traditional Chinese.
Buy on Gumroad โTypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad โYou Might Also Need
GraphQL Query Builder
Build GraphQL queries visually from your schema SDL. Select root fields, pick sub-fields, add arguments, and generate query/mutation/subscription strings instantly. Supports nested types.
Open free tool โ
WebSocket Tester
Connect to any WebSocket server, send and receive messages in real time, and inspect the message log with timestamps and byte sizes. Free online WebSocket client.
Open free tool โ
WebSocket Test Client
Connect to any WebSocket server, send and receive messages, and log all traffic with timestamps and byte sizes. Free browser-based WebSocket debugging tool โ no install needed.
Open free tool โ
More Free Tools
Related Articles
SWR vs TanStack Query vs Apollo vs urql: Data Fetching Comparison 2026
Compare SWR, TanStack Query, Apollo Client, and urql for React data fetching in 2026 โ caching strategies, bundle size, SSR support, GraphQL vs REST, and when each library wins.
GraphQL Federation vs REST Microservices: Which Architecture in 2026?
Compare GraphQL Federation vs REST microservices โ Apollo Federation, schema stitching, performance, team scaling, migration paths, and when to choose each in 2026.
GraphQL vs REST vs gRPC: A 2026 API Design Comparison
A practical guide comparing GraphQL vs REST vs gRPC for modern API development. Covers performance, type safety, tooling, use cases, and a decision framework for 2026.
tRPC vs GraphQL: End-to-End Type Safety in 2026
A comprehensive comparison of tRPC and GraphQL in 2026 โ developer experience, type safety, performance, schema definition, client libraries, real-time, and when to choose each for your fullstack TypeScript application.
GraphQL vs REST API: The Complete Developer Guide 2026
Deep-dive comparison of GraphQL vs REST: performance benchmarks, caching strategies, versioning approaches, tooling ecosystem, real migration stories, and a step-by-step decision tree to choose the right API for your project.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- โก AI Code Review, Doc Generator & SQL Builder
- โก All premium templates & early access
- โก Member discounts on Gumroad products
$9/mo after trial ยท Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products โ grab them all at once and save big.
MIT licensed ยท Instant download ยท No subscription
See what's included โ Browse all deals โFrequently Asked Questions
When should I use GraphQL subscriptions vs polling?▼
Use subscriptions when you need real-time push updates โ chat messages, live notifications, collaborative editing cursors, or order status changes. Use polling when the update frequency is low (every 30+ seconds), when real-time is not critical, or when infrastructure complexity is a concern. Polling is stateless and simpler to scale; subscriptions require persistent connections and stateful infrastructure (load balancers with sticky sessions or a pub/sub broker like Redis).
What is the graphql-ws protocol?▼
graphql-ws is the modern WebSocket subprotocol for GraphQL subscriptions (protocol identifier: graphql-transport-ws). It replaced the older subscriptions-transport-ws package which is no longer maintained. It handles connection initialization, ping/pong keepalives, subscription multiplexing over a single WebSocket, and graceful error handling. Apollo Server 4+ uses graphql-ws by default.
What are Server-Sent Events (SSE) and when should I use them over WebSockets?▼
Server-Sent Events (SSE) is an HTTP-based protocol for one-way server-to-client streaming. Unlike WebSockets (full-duplex), SSE is unidirectional โ only the server sends events. SSE works over standard HTTP/1.1 and HTTP/2, passes through proxies and firewalls more easily, and auto-reconnects natively in browsers. Use SSE for GraphQL subscriptions when you only need server-push (no client-initiated messages) and want simpler infrastructure. The graphql-sse package implements this.
How do I scale GraphQL subscriptions across multiple server instances?▼
Each server instance maintains its own set of WebSocket connections. When an event is published, you need all instances to be notified so they can push to their connected clients. This requires a pub/sub broker. The most common approach is Redis pub/sub: the publishing instance publishes to a Redis channel, all server instances subscribe to that channel and forward messages to their connected clients. graphql-redis-subscriptions implements this pattern with Apollo Server's PubSub interface.
Can I use subscriptions with Apollo Client?▼
Yes. Apollo Client has built-in subscription support via the useSubscription hook and subscribeToMore function on queries. You configure it with a WebSocketLink (using graphql-ws) in addition to your HttpLink for queries and mutations. A common pattern is to split the link: send subscription operations to WebSocketLink and everything else to HttpLink using Apollo's split() utility.
What are the security considerations for subscriptions?▼
Authenticate WebSocket connections during the connection_init message (the initial handshake), not at the HTTP upgrade step. Send the auth token in the connectionParams field and validate it in your onConnect server handler. Also implement authorization per-subscription in the resolver to ensure users only receive events for resources they have access to. Rate-limit the number of active subscriptions per user to prevent resource exhaustion.