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.
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.
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.
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.