install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/graphql-subscriptions" ~/.claude/skills/intense-visions-harness-engineering-graphql-subscriptions-fdaffd && rm -rf "$T"
manifest:
agents/skills/claude-code/graphql-subscriptions/SKILL.mdsource content
GraphQL Subscriptions
Implement real-time data streaming with GraphQL subscriptions over WebSocket connections
When to Use
- Pushing live updates to clients (chat messages, notifications, live scores)
- Replacing polling for frequently changing data
- Building collaborative features (shared cursors, live editing)
- Streaming long-running operation progress to the client
Instructions
- Define subscription types in the schema. Subscriptions are root-level operations alongside Query and Mutation.
type Subscription { messageAdded(channelId: ID!): Message! orderStatusChanged(orderId: ID!): Order! } type Message { id: ID! content: String! author: User! createdAt: DateTime! }
- Use
for the WebSocket transport. The oldergraphql-ws
is unmaintained.subscriptions-transport-ws
implements the GraphQL over WebSocket protocol correctly with proper connection lifecycle handling.graphql-ws
npm install graphql-ws ws
- Set up the WebSocket server alongside your HTTP server.
import { createServer } from 'http'; import { WebSocketServer } from 'ws'; import { useServer } from 'graphql-ws/lib/use/ws'; import { ApolloServer } from '@apollo/server'; import { expressMiddleware } from '@apollo/server/express4'; import { makeExecutableSchema } from '@graphql-tools/schema'; const schema = makeExecutableSchema({ typeDefs, resolvers }); const httpServer = createServer(app); const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql', }); const serverCleanup = useServer( { schema, context: async (ctx) => { const token = ctx.connectionParams?.authToken; return { currentUser: await authenticate(token as string) }; }, }, wsServer ); const server = new ApolloServer({ schema, plugins: [ { async serverWillStart() { return { async drainServer() { await serverCleanup.dispose(); }, }; }, }, ], });
- Implement a PubSub system for publishing events. Use an in-memory PubSub for development and a distributed backend (Redis, Kafka) for production.
import { PubSub } from 'graphql-subscriptions'; // Development only — in-memory, single-process const pubsub = new PubSub(); // Production — use Redis-backed PubSub import { RedisPubSub } from 'graphql-redis-subscriptions'; const pubsub = new RedisPubSub({ connection: { host: 'redis', port: 6379 }, });
- Write subscription resolvers with
and optionalsubscribe
. Theresolve
function returns an AsyncIterator. Thesubscribe
function transforms the published payload before sending to the client.resolve
const resolvers = { Subscription: { messageAdded: { subscribe: (_parent, { channelId }, { pubsub }) => { return pubsub.asyncIterator(`MESSAGE_ADDED.${channelId}`); }, resolve: (payload) => payload.messageAdded, }, }, Mutation: { sendMessage: async (_parent, { input }, { pubsub, dataSources }) => { const message = await dataSources.messages.create(input); await pubsub.publish(`MESSAGE_ADDED.${input.channelId}`, { messageAdded: message, }); return message; }, }, };
- Filter subscriptions with
. Only push events that match the subscriber's criteria — do not send all events and let the client discard irrelevant ones.withFilter
import { withFilter } from 'graphql-subscriptions'; const resolvers = { Subscription: { orderStatusChanged: { subscribe: withFilter( () => pubsub.asyncIterator('ORDER_STATUS_CHANGED'), (payload, variables) => payload.orderStatusChanged.id === variables.orderId ), }, }, };
-
Authenticate on the WebSocket connection, not per message. Extract the auth token from
during the initial WebSocket handshake. Reject unauthenticated connections in theconnectionParams
handler.onConnect -
Handle client-side subscriptions with
oruseSubscription
.subscribeToMore
// Standalone subscription const { data } = useSubscription(MESSAGE_ADDED, { variables: { channelId }, }); // Augment an existing query with live updates const { subscribeToMore, data } = useQuery(GET_MESSAGES, { variables: { channelId } }); useEffect(() => { return subscribeToMore({ document: MESSAGE_ADDED, variables: { channelId }, updateQuery: (prev, { subscriptionData }) => ({ messages: [...prev.messages, subscriptionData.data.messageAdded], }), }); }, [channelId, subscribeToMore]);
Details
In-memory PubSub limitations: The default
PubSub from graphql-subscriptions works only within a single Node.js process. In a multi-instance deployment, subscribers on one instance will not receive events published by another. Use Redis, Kafka, or Google PubSub for production.
Connection lifecycle:
graphql-ws supports onConnect (authentication, rate limiting), onDisconnect (cleanup), and onSubscribe (per-subscription validation). Use these hooks for access control.
Scaling considerations:
- Each active subscription holds an open WebSocket connection — plan for connection limits
- Use Redis PubSub for horizontal scaling across server instances
- Consider connection pooling and heartbeat intervals to detect stale connections
- Load balancers must support WebSocket upgrade (sticky sessions or Layer 4 balancing)
Alternatives to subscriptions:
- Server-Sent Events (SSE) for one-way streaming without WebSocket infrastructure
and@defer
directives for incremental delivery of query results (experimental)@stream- Polling with
for simple cases with moderate freshness needscache-and-network
Source
https://the-guild.dev/graphql/ws
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.