Catch the highlights of GraphQLConf 2023! Click for recordings. Or check out our recap blog post.
Docs
Advanced
Subscriptions

Subscriptions

Subscription is a third kind of GraphQL operation, next to Query and Mutation. Think of it as an event emitter where events are emitted by your backend and consumed by a GraphQL client-frontend in most scenarios.

GraphQL is just a specification and GraphQL Yoga or Apollo Server are Node implementations of the server-side part of GraphQL. A natural fit for Subscriptions is the WebSocket protocol, but it could be anything. We're going to focus only on WS and use subscriptions-transport-ws package as our transport layer.

Setup

For Queries and Mutations, we use createExecution() method to create an execution logic, in the case of Subscriptions it's very similar. We construct the subscription phase with createSubscription() method and provide it to a GraphQL server.

import { createApplication } from 'graphql-modules'
 
const application = createApplication({
  modules: [
    // ...
  ]
})
 
const subscribe = application.createSubscription()
💡

createSubscription() accepts an object with subscribe property in case you want to use your own subscription logic. It uses subscribe from graphql by default.

Why do we need it? GraphQL Modules need to understand the life cycle of a subscription to avoid memory leaks.

Example

We recommend using graphql-subscriptions as it provides a handful utility functions (filtering for example) and an event emitter.

Provide PubSub with an instance as the value so as a singleton service it becomes available across all modules.

application.ts
import { createApplication } from 'graphql-modules'
import { PubSub } from 'graphql-subscriptions'
import { myModule } from './my-module'
 
const application = createApplication({
  modules: [myModule /* ... */],
  providers: [
    {
      provide: PubSub,
      useValue: new PubSub()
    }
  ]
})

Accessing PubSub in a module can be done both, in a resolve function and Messages service.

Take a look at two things here, how MESSAGE_ADDED event was emitter in Messages.send() and how Subscriptions.messageAdded subscribes to events.

my-module.ts
import { createModule, Injectable, gql } from 'graphql-modules'
import { PubSub } from 'graphql-subscriptions'
 
const MESSAGE_ADDED = 'MESSAGE_ADDED'
 
const messages = [
  // ...
]
 
@Injectable()
class Messages {
  constructor(private pubsub: PubSub) {}
 
  async all() {
    return messages
  }
 
  async send(body: string) {
    const message = {
      id: generateRandomId(),
      body
    }
 
    messages.push(message)
 
    this.pubsub.publish(MESSAGE_ADDED, { messageAdded: message })
 
    return message
  }
}
 
export const myModule = createModule({
  id: 'my-module',
  providers: [Messages],
  typeDefs: gql`
    type Query {
      messages: [Message!]
    }
 
    type Mutation {
      sendMessage(message: String!): Message!
    }
 
    type Subscription {
      messageAdded: Message
    }
 
    type Message {
      id: ID!
      body: String!
    }
  `,
  resolvers: {
    Query: {
      messages(parent, args, ctx: GraphQLModules.Context) {
        return ctx.injector.get(Messages).all()
      }
    },
    Mutation: {
      sendMessage(parent, { message }, ctx: GraphQLModules.Context) {
        return ctx.injector.get(Messages).send(message)
      }
    },
    Subscription: {
      messageAdded: {
        subscribe(root, args, ctx: GraphQLModules.Context) {
          return ctx.injector.get(PubSub).asyncIterator([MESSAGE_ADDED])
        }
      }
    }
  }
})

Here are reference implementations of using GraphQL Subscriptions with WebSockets in both, Apollo Server and Express GraphQL.

server.ts
import 'reflect-metadata'
import { createServer } from '@graphql-yoga/node'
import { useGraphQLModules } from '@envelop/graphql-modules'
import { application } from './application'
 
const server = createServer({
  plugins: [useGraphQLModules(application)]
})
 
server.start().then(() => {
  console.log(`🚀 Server ready`)
})
💡

GraphQL Yoga uses SSE by default, if you want to use WebSockets instead, follow these instructions.