Real-time GraphQL with Subscriptions in Node.js

Table of contents
- Understanding GraphQL Subscriptions
- My Use Case: Live Chat
- Project File Structure
- Initial Setup
- Step 1: Define the GraphQL Schema and Resolvers
- Step 2: Setup Apollo Server and WebSocket Integration
- Step 3: Frontend Client Setup (Apollo + WebSockets)
- Testing the System
- What I Learned
- Gotchas and Tips
- Final Thoughts
When I built my first GraphQL server, everything felt elegant and predictable — queries fetched data, mutations changed data, and that was it. But then I was asked to add a feature for real-time notifications. Suddenly, I needed something GraphQL didn’t seem to support out of the box.
That’s when I discovered GraphQL Subscriptions.
Subscriptions let you open a live connection between the client and server so updates can be pushed in real-time. They’re perfect for use cases like chat apps, stock tickers, live sports scores, or notification systems. In this post, I’ll walk through how I implemented real-time GraphQL subscriptions in a Node.js backend — using WebSockets, Apollo Server, and a simple pub/sub system.
Understanding GraphQL Subscriptions
In GraphQL, subscriptions are a third type of operation alongside queries and mutations. But unlike queries or mutations, subscriptions use WebSockets instead of HTTP. This persistent connection allows the server to push data to clients the moment something changes.
In a typical subscription, the client says:
"Hey server, let me know whenever a new message is sent to chat room #general."
The server then keeps that connection open and sends new data as soon as it becomes available. This is extremely powerful when you want real-time feedback without constantly polling the server.
My Use Case: Live Chat
To explore this, I built a simple chat system. It had two operations:
Mutation: Send a message
Subscription: Receive new messages as they’re sent
This allowed multiple clients to chat in real-time using WebSockets behind the scenes.
Project File Structure
/realtime-chat-app
├── src
│ ├── index.js # Entry point for Apollo + WebSocket server
│ ├── schema.js # GraphQL typeDefs and resolvers
│ └── pubsub.js # PubSub instance
├── package.json
Initial Setup
npm install apollo-server graphql graphql-subscriptions graphql-ws ws uuid
Step 1: Define the GraphQL Schema and Resolvers
📄 File: src/schema.js
const { gql } = require('apollo-server');
const { PubSub } = require('graphql-subscriptions');
const { v4: uuid } = require('uuid');
const pubsub = new PubSub();
const messages = [];
const typeDefs = gql`
type Message {
id: ID!
content: String!
sender: String!
}
type Query {
messages: [Message!]!
}
type Mutation {
sendMessage(content: String!, sender: String!): Message!
}
type Subscription {
messageSent: Message!
}
`;
const resolvers = {
Query: {
messages: () => messages,
},
Mutation: {
sendMessage: (_, { content, sender }) => {
const message = { id: uuid(), content, sender };
messages.push(message);
pubsub.publish('MESSAGE_SENT', { messageSent: message });
return message;
},
},
Subscription: {
messageSent: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_SENT']),
},
},
};
module.exports = { typeDefs, resolvers, pubsub };
Step 2: Setup Apollo Server and WebSocket Integration
📄 File: src/index.js
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const http = require('http');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');
const { typeDefs, resolvers } = require('./schema');
const app = express();
const httpServer = http.createServer(app);
const schema = makeExecutableSchema({ typeDefs, resolvers });
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});
useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
});
async function startServer() {
await server.start();
server.applyMiddleware({ app });
const PORT = 4000;
httpServer.listen(PORT, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`);
});
}
startServer();
Step 3: Frontend Client Setup (Apollo + WebSockets)
📄 Client-side (React app example)
import { createClient } from 'graphql-ws';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/graphql',
}));
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(),
});
Testing the System
Once I had both sides wired up, I opened two browser windows and sent a message from one. Instantly, the other window received the update without refreshing or polling.
That moment was incredibly satisfying. After all the moving parts — HTTP, WebSocket, resolvers, and pub/sub — it all clicked together.
What I Learned
Subscriptions are event-driven, not request-driven.
Keep pub/sub logic clean — use Redis or Kafka for scalability later.
Apollo doesn’t handle WebSockets directly — you need
graphql-ws
.The schema and resolvers stay mostly the same as queries/mutations.
Subscriptions are perfect for live data use cases.
Gotchas and Tips
Make sure your WebSocket server runs on the correct path (
/graphql
).GraphQL Playground doesn’t support subscriptions — use Apollo Client or GraphiQL instead.
Memory-based PubSub is fine for demos but not production-scale.
Remember to handle cleanup when clients disconnect from WebSocket.
Final Thoughts
GraphQL subscriptions opened a whole new dimension in how I build apps. They take a bit of setup, especially compared to queries and mutations, but the real-time interactivity is well worth it.
Whether you're building a multiplayer game, a live news feed, or a collaborative document editor, subscriptions give you the power to react to data changes as they happen. And the best part? You don’t have to leave the GraphQL ecosystem.
** App may not work properly, will update this post with examples as well
Subscribe to my newsletter
Read articles from Sharukhan Patan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
