Launch Apollo Studio

Subscriptions in Apollo Server

Persistent GraphQL read operations


Apollo Server 3 removes built-in support for subscriptions. You can reenable support as described below.

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.

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.

Enabling subscriptions

Beginning in Apollo Server 3, subscriptions are not supported by the "batteries-included" apollo-server package. To enable subscriptions, you must first swap to the apollo-server-express package (or any other Apollo Server integration package that supports subscriptions).

The following steps assume you've already swapped to apollo-server-express.

To run both an Express app and a separate subscription server, we'll create an http.Server instance that effectively wraps the two and becomes our new listener. We'll also create a SubscriptionServer, which requires a few tweaks to our existing code.

  1. Install subscriptions-transport-ws and @graphql-tools/schema:

    npm install subscriptions-transport-ws @graphql-tools/schema
  2. Add the following imports to the file where you initialize your ApolloServer instance (we'll use these in later steps):

    index.js
    import { createServer } from 'http';
    import { execute, subscribe } from 'graphql';
    import { SubscriptionServer } from 'subscriptions-transport-ws';
    import { makeExecutableSchema } from '@graphql-tools/schema';
  3. Next, in order to set up both the HTTP and WebSocket servers, we need to create an http.Server. Do this by passing your Express app to the createServer function, which we imported from the http module:

    index.js
    // This `app` is the returned value from `express()`.
    const httpServer = createServer(app);
  4. Create an instance of GraphQLSchema (if you don't already).

    If you already pass the schema option to the ApolloServer constructor (instead of typeDefs and resolvers), you can skip this step.

    The SubscriptionServer (which we'll instantiate next) doesn't take typeDefs and resolvers options. Instead, it takes an executable GraphQLSchema. We can pass this schema object to both the SubscriptionServer and ApolloServer. This way, we make sure that the same schema is being used in both places.

    index.js
    const schema = makeExecutableSchema({ typeDefs, resolvers });
    // ...
    const server = new ApolloServer({
      schema,
    });
  5. Create the SubscriptionServer.

    index.js
    const subscriptionServer = SubscriptionServer.create({
       // This is the `schema` we just created.
       schema,
       // These are imported from `graphql`.
       execute,
       subscribe,
    }, {
       // This is the `httpServer` we created in a previous step.
       server: httpServer,
       // Pass a different path here if your ApolloServer serves at
       // a different path.
       path: '/graphql',
    });
  6. Add a plugin to your ApolloServer constructor to close the SubscriptionServer.

    index.js
    const server = new ApolloServer({
      schema,
      plugins: [{
        async serverWillStart() {
          return {
            async drainServer() {
              subscriptionServer.close();
            }
          };
        }
      }],
    });
  7. Finally, we need to adjust our existing listen call.

    Most Express applications call app.listen(...). Change this to httpServer.listen(...) with the same arguments. This way, the server starts listening on the HTTP and WebSocket transports simultaneously.

A completed example of migrating subscriptions is shown below:

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:

index.js
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 abstract PubSubEngine class. Recommended subclasses are listed in Production PubSub libraries.

Apollo Server uses a publish-subscribe (pub/sub) model to track events that update active subscriptions. The graphql-subscriptions library provides the PubSub class as a basic in-memory event bus to help you get started:

To use the graphql-subscriptions package, first install it like so:

npm install graphql-subscriptions

A PubSub instance enables your server code to both publish events to a particular label and listen for events associated with a particular label. We can create a PubSub instance like so:

import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

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.

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

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:

index.js
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:

import { withFilter } from 'graphql-subscriptions';

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, and false 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:

Edit server-subscriptions-as3

The server exposes one subscription (numberIncremented) that returns an integer that's incremented on the server every second. Here's an example subscription that you can run against your server:

subscription IncrementingNumber {
  numberIncremented
}

After you start up the server in CodeSandbox, follow the instructions in the browser to test out running the numberIncremented subscription in Apollo Sandbox. You'll probably need to configure the subscriptions endpoint (wss://YOUR_SANDBOX_ID.sse.codesandbox.io/graphql) in Sandbox's Settings tab. You'll see the subscription's value update every second.

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 provide an onConnect function to the SubscriptionServer constructor. This function receives the connectionParams in an object as the first argument, as well as the WebSocket and ConnectionContext as the second and third arguments, respectively. If your onConnect function returns an object, it's passed to your resolvers as the context argument.

SubscriptionServer.create({
  // ...other options
  async onConnect(connectionParams) {
    if (connectionParams.authorization) {
      const currentUser = await findUser(connectionParams.authorization);
      return { currentUser };
    }
    throw new Error('Missing auth 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 the subscription 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 in onConnect. This is especially useful for authentication.
  • If onConnect returns an object, that object is passed as the operation context to your resolvers.

You provide these function definitions to the constructor object of a SubscriptionServer, like so:

SubscriptionServer.create({
  schema,
  execute,
  subscribe,
  onConnect(connectionParams, webSocket, context) {    console.log('Connected!')
  },
  onDisconnect(webSocket, context) {
    console.log('Disconnected!')
  },});

These functions are passed the following parameters:

Name /
Type
Description
connectionParams

Object

Passed to onConnect only.

An object containing parameters included in the request, such as an authentication token.

For details, see Authenticate over WebSocket in the Apollo Client documentation.

webSocket

WebSocket

The connecting or disconnecting WebSocket.

context

ConnectionContext

Context object for the WebSocket connection. This is not the context object for the associated subscription operation.

See fields

Example: Authentication with onConnect

On the client, SubscriptionsClient supports adding information to connectionParams (example) that will be sent with the first WebSocket message. On the server, all GraphQL subscriptions are delayed until the connection has been fully authenticated and the onConnect callback returns a truthy value.

Let's suppose we have our SubscriptionClient configured like so:

new SubscriptionClient(subscriptionUrl, {
  // ...other options
  connectionParams: {
    authorization: clientToken,
  },
})

The connectionParams argument in the SubscriptionServer's onConnect callback contains the information passed by the client and can be used to validate user credentials.

From the onConnect function, we can then return an object which is passed into our resolvers as the context argument during execution. For our example, we can use the authorization token provided by the client to look up the related user and pass that user along to our resolvers.

async function findUser(authToken) {
  // find a user by auth token
};

SubscriptionServer.create({
  schema,
  execute,
  subscribe,
  async onConnect(connectionParams, webSocket) {
    if (connectionParams.authorization) {
      const currentUser = await findUser(connectionParams.authorization);
      return { currentUser };
    }
    throw new Error('Missing auth token!');
  },
  { server, path }
});

The example above looks up a user based on the auth token that is sent with the first initialization message on the transport, then 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.

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!

The graphql-ws transport library

This page explains how to connect the subscriptions-transport-ws library to your Apollo Server. However, as of 2021, this library is not actively maintained. A newer alternative to subscriptions-transport-ws called graphql-ws is currently more actively maintained, and its README explains how to use it with Apollo Server. The two libraries implement different protocols for GraphQL subscriptions over websockets, so you will need to adjust your client to support graphql-ws. At the time of release of Apollo Server 3, the protocol used in the graphql-ws library is not yet supported by GraphQL Playground or Apollo Explorer, which is why we do not yet document it as our main recommendation for a subscriptions library. If the ability to interact with subscriptions in GraphQL Playground or Apollo Explorer is not important to you, you may prefer using graphql-ws over subscriptions-transport-ws.

Edit on GitHub