Subscriptions
Persistent GraphQL read operations
Subscriptions are not currently supported in Apollo Federation.
Subscriptions are long-lasting GraphQL read operations that can update their result whenever a particular server-side event occurs. Most commonly, updated results are pushed from the server to subscribing clients. For example, a chat application's server might use a subscription to push newly received messages to all clients in a particular chat room.
Because subscription updates are usually pushed by the server (instead of polled by the client), they usually use the WebSocket protocol instead of HTTP. To support this, Apollo Server lets you set a subscription-specific endpoint that's separate from the default endpoint for queries and mutations.
You can use subscriptions with the core apollo-server
library, or with any of Apollo Server's supported middleware integrations.
Important: Compared to queries and mutations, subscriptions are significantly more complex to implement. Before you begin, confirm that your use case requires subscriptions.
Schema definition
Your schema's Subscription
type defines top-level fields that clients can subscribe to:
type Subscription {
postCreated: Post
}
The postCreated
field will update its value whenever a new Post
is created on the backend, thus pushing the Post
to subscribing clients.
Clients can subscribe to the postCreated
field with a GraphQL string like this:
subscription PostFeed {
postCreated {
author
comment
}
}
Each subscription operation can subscribe to only one field of the
Subscription
type.
Setting a subscription endpoint
Because subscriptions use WebSocket instead of HTTP, Apollo Server uses a second GraphQL endpoint specifically for subscriptions. This endpoint uses the ws
protocol instead of http
.
By default, the subscription endpoint's path matches the path of your primary GraphQL endpoint (/graphql
if not set). You can specify a different path for your subscription endpoint like so:
const server = new ApolloServer({
subscriptions: {
path: '/subscriptions'
},
// ...other options...
}));
Resolving a subscription
Resolvers for Subscription
fields differ from resolvers for fields of other types. Specifically, Subscription
field resolvers are objects that define a subscribe
function:
const resolvers = {
Subscription: {
postCreated: {
// More on pubsub below
subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
},
},
// ...other resolvers...
};
The subscribe
function must return an object of type AsyncIterator
, a standard interface for iterating over asynchronous results. In the example above, an AsyncIterator
is generated by pubsub.asyncIterator
(more on this below).
The PubSub
class
The
PubSub
class is not recommended for production environments, because it's an in-memory event system that only supports a single server instance. After you get subscriptions working in development, we strongly recommend switching it out for a different subclass of the abstractPubSubEngine
class. Recommended subclasses are listed in ProductionPubSub
libraries.
Apollo Server uses a publish-subscribe (pub/sub) model to track events that update active subscriptions. The graphql-subscriptions
library (included in every apollo-server
package) provides the PubSub
class as a basic in-memory event bus to help you get started:
const { PubSub } = require('apollo-server');
const pubsub = new PubSub();
A PubSub
instance enables your server code to both publish
events to a particular label and listen for events associated with a particular label.
Publishing an event
You publish an event with the publish
method of a PubSub
instance:
pubsub.publish('POST_CREATED', {
postCreated: {
author: 'Ali Baba',
comment: 'Open sesame'
}
});
- The first parameter is the name of the event label you're publishing to, as a string.
- You don't need to register a label name before publishing to it.
- The second parameter is the payload associated with the event.
- The payload should include whatever data is necessary for your resolvers to populate the associated
Subscription
field and its subfields.
- The payload should include whatever data is necessary for your resolvers to populate the associated
When working with GraphQL subscriptions, you publish
an event whenever a subscription's return value should be updated. One common cause of such an update is a mutation, but any back-end logic might result in changes that should be publish
ed.
As an example, let's say our GraphQL API supports a createPost
mutation:
type Mutation {
createPost(author: String, comment: String): Post
}
A basic resolver for createPost
might look like this:
const resolvers = {
Mutation: {
createPost(parent, args, context) { // Datastore logic lives in postController return postController.createPost(args); }, },
// ...other resolvers...
};
Before we persist the new post's details in our datastore, we can publish
an event that also includes those details:
const resolvers = {
Mutation: {
createPost(parent, args, context) {
pubsub.publish('POST_CREATED', { postCreated: args }); return postController.createPost(args);
},
},
// ...other resolvers...
};
Next, we can listen for this event in our Subscription
field's resolver.
Listening for events
An AsyncIterator
object listens for events that are associated with a particular label (or set of labels) and adds them to a queue for processing. You create an AsyncIterator
by calling the asyncIterator
method of PubSub
:
pubsub.asyncIterator(['POST_CREATED']);
You pass this method an array containing the names of all event labels that the AsyncIterator
should listen for.
Every Subscription
field resolver's subscribe
function must return an AsyncIterator
object. This brings us back to the code sample at the top of Resolving a subscription:
const resolvers = {
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
},
},
// ...other resolvers...
};
With this subscribe
function set, Apollo Server uses the payloads of POST_CREATED
events to push updated values for the postCreated
field.
Filtering events
Sometimes, a client should only receive updated subscription data if that data meets certain criteria. To support this, you can call the withFilter
helper function in your Subscription
field's resolver.
Example
Let's say our server provides a commentAdded
subscription, which should notify clients whenever a comment is added to a specified code repository. A client can execute a subscription that looks like this:
subscription($repoName: String!){
commentAdded(repoFullName: $repoName) {
id
content
}
}
This presents a potential issue: our server probably publishes a COMMENT_ADDED
event whenever a comment is added to any repository. This means that the commentAdded
resolver executes for every new comment, regardless of which repository it's added to. As a result, subscribing clients might receive data they don't want (or shouldn't even have access to).
To fix this, we can use the withFilter
helper function to control updates on a per-client basis.
Here's an example resolver for commentAdded
that uses the withFilter
function:
const { withFilter } = require('apollo-server');
const resolvers = {
Subscription: {
commentAdded: { subscribe: withFilter( () => pubsub.asyncIterator('COMMENT_ADDED'), (payload, variables) => { // Only push an update if the comment is on // the correct repository for this operation return (payload.commentAdded.repository_name === variables.repoFullName); }, ),
}
},
// ...other resolvers...
};
The withFilter
function takes two parameters:
- The first parameter is exactly the function you would use for
subscribe
if you weren't applying a filter. - The second parameter is a filter function that returns
true
if a subscription update should be sent to a particular client, andfalse
otherwise (Promise<boolean>
is also allowed). This function takes two parameters of its own:payload
is the payload of the event that was published.variables
is an object containing all arguments the client provided when initiating their subscription.
Use withFilter
to make sure clients get exactly the subscription updates they want (and are allowed to receive).
Basic runnable example
An example server is available on GitHub and CodeSandbox:
The server exposes one subscription (numberIncremented
) that returns an integer that's incremented on the server every second. The example requires only the apollo-server
library.
After you start up this server, you can test out running a subscription with the Apollo Studio Explorer or GraphQL Playground, as described in the full-stack tutorial. You'll see the subscription's value update every second.
In the Apollo Studio Explorer, you must specify your server's subscription endpoint (
ws://localhost:4000/subscriptions
) in the Explorer Settings tab.
Operation context
When initializing context for a query or mutation, you usually extract HTTP headers and other request metadata from the req
object provided to the context
function.
For subscriptions, you extract this metadata from the connection
object instead. This object adheres to the ExecutionParams
interface.
Because all operation types use the same context
initialization function, you should check which of req
or connection
is present for each incoming request:
const server = new ApolloServer({
context: ({ req, connection }) => {
if (connection) { // Operation is a Subscription
// Obtain connectionParams-provided token from connection.context
const token = connection.context.authorization || "";
return { token };
} else { // Operation is a Query/Mutation
// Obtain header-provided token from req.headers
const token = req.headers.authorization || "";
return { token };
}
},
});
This is especially important because metadata like auth tokens are sent differently depending on the transport.
onConnect
and onDisconnect
You can define functions that Apollo Server executes whenever a subscription request connects (onConnect
) or disconnects (onDisconnect
).
Defining an onConnect
function provides the following benefits:
- You can reject a particular incoming connection by throwing an exception or returning
false
inonConnect
.- This is especially useful for authentication.
- If
onConnect
returns an object, that object's fields are added to the WebSocket connection'scontext
object.- This is not the operation
context
that's passed between resolvers. However, you can transfer these values from theconnection
'scontext
when you initialize operation context.
- This is not the operation
You provide these function definitions to the constructor of ApolloServer
, like so:
const server = new ApolloServer({
subscriptions: {
onConnect: (connectionParams, webSocket, context) => {
console.log('Connected!')
},
onDisconnect: (webSocket, context) => {
console.log('Disconnected!')
},
// ...other options...
},
});
These functions are passed the following parameters:
Name / Type | Description |
---|---|
| Passed to An object containing parameters included in the request, such as an authentication token. For details, see Authenticate over WebSocket in the Apollo Client documentation. |
| The connecting or disconnecting |
| Context object for the WebSocket connection. This is not the |
Example: Authentication with onConnect
On the client, SubscriptionsClient
supports adding token information to connectionParams
(example) that will be sent with the first WebSocket message. In the server, all GraphQL subscriptions are delayed until the connection has been fully authenticated and the onConnect
callback returns a truthy value.
The connectionParams
argument in the onConnect
callback contains the information passed by the client and can be used to validate user credentials.
The GraphQL context can also be extended with the authenticated user data to enable fine grain authorization.
const { ApolloServer } = require('apollo-server');
const { resolvers, typeDefs } = require('./schema');
const validateToken = authToken => {
// ... validate token and return a Promise, rejects in case of an error
};
const findUser = authToken => {
return tokenValidationResult => {
// ... finds user by auth token and return a Promise, rejects in case of an error
};
};
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
onConnect: (connectionParams, webSocket) => {
if (connectionParams.authToken) {
return validateToken(connectionParams.authToken)
.then(findUser(connectionParams.authToken))
.then(user => {
return {
currentUser: user,
};
});
}
throw new Error('Missing auth token!');
},
},
});
server.listen().then(({ url, subscriptionsUrl }) => {
console.log(`🚀 Server ready at ${url}`);
console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
});
The example above validates the user's token that is sent with the first initialization message on the transport, then it looks up the user and returns the user object as a Promise. The user object found will be available as context.currentUser
in your GraphQL resolvers.
In case of an authentication error, the Promise will be rejected, which prevents the client's connection.
Using with middleware integrations
You can use subscriptions with any of Apollo Server's supported middleware integrations. To do so, you call installSubscriptionHandlers
on your ApolloServer
instance.
This example enables subscriptions for an Express server that uses apollo-server-express
:
const http = require('http');
const { ApolloServer } = require('apollo-server-express');
const express = require('express');
async function startApolloServer() {
const PORT = 4000;
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
server.applyMiddleware({app})
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
// Make sure to call listen on httpServer, NOT on app.
await new Promise(resolve => httpServer.listen(PORT, resolve));
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
return { server, app, httpServer };
}
Production PubSub
libraries
As mentioned above, the PubSub
class is not recommended for production environments, because its event-publishing system is in-memory. This means that events published by one instance of your GraphQL server are not received by subscriptions that are handled by other instances.
Instead, you should use a subclass of the PubSubEngine
abstract class that you can back with an external datastore such as Redis or Kafka.
The following are community-created PubSub
libraries for popular event-publishing systems:
If none of these libraries fits your use case, you can also create your own PubSubEngine
subclass. If you create a new open-source library, click Edit on GitHub to let us know about it!