Table of Contents
- What Are GraphQL Subscriptions?
- Real-time vs Traditional Data Fetching
- WebSockets and GraphQL
- Setting Up GraphQL Subscriptions in Node.js
- Using Apollo Server with Subscriptions
- Broadcasting Events with PubSub
- Example: Chat Application with Subscriptions
- Authentication in Subscriptions
- Scaling Subscriptions in Production
- Best Practices and Considerations
1. What Are GraphQL Subscriptions?
GraphQL Subscriptions enable real-time communication between a client and server. Unlike queries and mutations, which follow a request-response cycle, subscriptions use WebSockets to maintain a persistent connection, allowing the server to push updates to the client whenever a specific event occurs.
2. Real-time vs Traditional Data Fetching
Traditional GraphQL:
- Client makes a request.
- Server sends back data.
- Connection ends.
GraphQL with Subscriptions:
- Client subscribes to an event.
- Server pushes new data whenever the event happens.
- Persistent WebSocket connection remains open.
3. WebSockets and GraphQL
To implement GraphQL subscriptions, WebSockets are commonly used. WebSocket provides a full-duplex communication channel, which is perfect for pushing real-time updates from the server to connected clients.
Popular libraries for this include:
graphql-ws
(modern, lightweight, recommended)subscriptions-transport-ws
(deprecated)
4. Setting Up GraphQL Subscriptions in Node.js
Prerequisites:
- Node.js
- Apollo Server
- graphql-ws
- graphql
Install Dependencies:
npm install apollo-server graphql graphql-ws ws
5. Using Apollo Server with Subscriptions
Apollo Server v3+ does not handle WebSockets directly. You need to integrate it with graphql-ws
and an HTTP/WebSocket server manually.
Basic Setup:
const { createServer } = require('http');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');
const { ApolloServer } = require('apollo-server');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = `
type Message {
content: String
}
type Query {
_empty: String
}
type Subscription {
messageSent: Message
}
`;
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();
const resolvers = {
Subscription: {
messageSent: {
subscribe: () => pubsub.asyncIterator('MESSAGE_SENT')
}
}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({ schema });
const httpServer = createServer();
(async () => {
await server.start();
server.applyMiddleware({ app: httpServer });
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
useServer({ schema }, wsServer);
httpServer.listen(4000, () => {
console.log('Server running on http://localhost:4000/graphql');
});
})();
6. Broadcasting Events with PubSub
To broadcast data to all subscribed clients, use a pub-sub pattern.
Example Trigger:
setInterval(() => {
pubsub.publish('MESSAGE_SENT', {
messageSent: { content: "New message at " + new Date().toISOString() }
});
}, 5000);
Clients listening to messageSent
will receive updates every 5 seconds.
7. Example: Chat Application with Subscriptions
Schema:
type Message {
id: ID!
content: String!
sender: String!
}
type Mutation {
sendMessage(content: String!, sender: String!): Message
}
type Subscription {
messageSent: Message
}
Resolver:
const resolvers = {
Mutation: {
sendMessage: (_, { content, sender }) => {
const message = { id: Date.now(), content, sender };
pubsub.publish('MESSAGE_SENT', { messageSent: message });
return message;
},
},
Subscription: {
messageSent: {
subscribe: () => pubsub.asyncIterator('MESSAGE_SENT'),
},
},
};
8. Authentication in Subscriptions
WebSockets don’t send HTTP headers after the initial handshake. To authenticate:
- Send a token in the connection payload.
- Validate the token before accepting the connection.
Example:
useServer({
schema,
onConnect: async (ctx) => {
const token = ctx.connectionParams?.authToken;
if (!validateToken(token)) throw new Error("Unauthorized");
}
}, wsServer);
9. Scaling Subscriptions in Production
WebSocket-based subscriptions can be tricky to scale across multiple instances.
Common strategies:
- Redis PubSub: Share pub/sub events across servers.
- Apollo Federation with Subscription Gateway
- Use managed services like Hasura or GraphQL APIs with AWS AppSync.
Redis Example:
bashCopyEditnpm install graphql-redis-subscriptions ioredis
const { RedisPubSub } = require('graphql-redis-subscriptions');
const Redis = require('ioredis');
const pubsub = new RedisPubSub({
publisher: new Redis(),
subscriber: new Redis(),
});
10. Best Practices and Considerations
Practice | Description |
---|---|
Use graphql-ws | Avoid deprecated libraries |
Always authenticate | Use JWT or session tokens in connectionParams |
Implement rate limiting | Prevent abuse or spam |
Use Redis for scale | Scale subscriptions across clusters |
Prefer Subscriptions for small payloads | Don’t overuse it for large datasets |
Graceful fallback | Provide polling as fallback when WebSocket is unavailable |
Conclusion
GraphQL Subscriptions unlock powerful real-time capabilities in your Node.js applications, from chat apps to collaborative tools. By combining WebSocket protocols, graphql-ws
, and event broadcasting with robust authentication and scaling strategies, you can build reliable and responsive real-time systems.