Subscriptions in Apollo Server

Persistent GraphQL read operations


Apollo Server does not provide built-in support for subscriptions. You can enable support for subscriptions as described below.This article uses the graphql-ws library to add support for subscriptions to Apollo Server 4. We no longer recommend using the previously documented subscriptions-transport-ws, because this library is not actively maintained. For more information about the differences between the two libraries, see Switching from subscriptions-transport-ws.

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 generally 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:

GraphQL
1type Subscription {
2  postCreated: Post
3}

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:

GraphQL
1subscription PostFeed {
2  postCreated {
3    author
4    comment
5  }
6}

Each subscription operation can subscribe to only one field of the Subscription type.

Enabling subscriptions

Subscriptions are not supported by Apollo Server 4's startStandaloneServer function. To enable subscriptions, you must first swap to using the expressMiddleware function (or any other Apollo Server integration package that supports subscriptions).

The following steps assume you've already swapped to expressMiddleware.

To run both an Express app and a separate WebSocket server for subscriptions, we'll create an http.Server instance that effectively wraps the two and becomes our new listener.

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

    Bash
    1npm install graphql-ws 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):

    TypeScript
    index.ts
    1import { createServer } from 'http';
    2import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
    3import { makeExecutableSchema } from '@graphql-tools/schema';
    4import { WebSocketServer } from 'ws';
    5import { useServer } from 'graphql-ws/lib/use/ws';
  3. Next, in order to set up both the HTTP and subscription servers, we need to first create an http.Server. Do this by passing your Express app to the createServer function, which we imported from the http module:

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

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

    The subscription server (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 subscription server and ApolloServer. This way, we make sure that the same schema is being used in both places.

    TypeScript
    index.ts
    1const schema = makeExecutableSchema({ typeDefs, resolvers });
    2// ...
    3const server = new ApolloServer({
    4  schema,
    5});
  5. Create a WebSocketServer to use as your subscription server.

    TypeScript
    index.ts
    1// Creating the WebSocket server
    2const wsServer = new WebSocketServer({
    3  // This is the `httpServer` we created in a previous step.
    4  server: httpServer,
    5  // Pass a different path here if app.use
    6  // serves expressMiddleware at a different path
    7  path: '/subscriptions',
    8});
    9
    10// Hand in the schema we just created and have the
    11// WebSocketServer start listening.
    12const serverCleanup = useServer({ schema }, wsServer);
  6. Add plugins to your ApolloServer constructor to shutdown both the HTTP server and the WebSocketServer:

    TypeScript
    index.ts
    1const server = new ApolloServer({
    2  schema,
    3  plugins: [
    4    // Proper shutdown for the HTTP server.
    5    ApolloServerPluginDrainHttpServer({ httpServer }),
    6
    7    // Proper shutdown for the WebSocket server.
    8    {
    9      async serverWillStart() {
    10        return {
    11          async drainServer() {
    12            await serverCleanup.dispose();
    13          },
    14        };
    15      },
    16    },
    17  ],
    18});
  7. Finally, ensure you are listening to your httpServer.

    Most Express applications call app.listen(...), but for your setup change this to httpServer.listen(...) using the same arguments. This way, the server starts listening on the HTTP and WebSocket transports simultaneously.

A completed example of setting up subscriptions is shown below:

Click to expand
TypeScript
index.ts
1import { ApolloServer } from '@apollo/server';
2import { expressMiddleware } from '@apollo/server/express4';
3import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
4import { createServer } from 'http';
5import express from 'express';
6import { makeExecutableSchema } from '@graphql-tools/schema';
7import { WebSocketServer } from 'ws';
8import { useServer } from 'graphql-ws/lib/use/ws';
9import cors from 'cors';
10import resolvers from './resolvers';
11import typeDefs from './typeDefs';
12
13// Create the schema, which will be used separately by ApolloServer and
14// the WebSocket server.
15const schema = makeExecutableSchema({ typeDefs, resolvers });
16
17// Create an Express app and HTTP server; we will attach both the WebSocket
18// server and the ApolloServer to this HTTP server.
19const app = express();
20const httpServer = createServer(app);
21
22// Create our WebSocket server using the HTTP server we just set up.
23const wsServer = new WebSocketServer({
24  server: httpServer,
25  path: '/subscriptions',
26});
27// Save the returned server's info so we can shutdown this server later
28const serverCleanup = useServer({ schema }, wsServer);
29
30// Set up ApolloServer.
31const server = new ApolloServer({
32  schema,
33  plugins: [
34    // Proper shutdown for the HTTP server.
35    ApolloServerPluginDrainHttpServer({ httpServer }),
36
37    // Proper shutdown for the WebSocket server.
38    {
39      async serverWillStart() {
40        return {
41          async drainServer() {
42            await serverCleanup.dispose();
43          },
44        };
45      },
46    },
47  ],
48});
49
50await server.start();
51app.use(
52  '/graphql',
53  cors<cors.CorsRequest>(),
54  express.json(),
55  expressMiddleware(server),
56);
57
58const PORT = 4000;
59// Now that our HTTP server is fully set up, we can listen to it.
60httpServer.listen(PORT, () => {
61  console.log(`Server is now running on http://localhost:${PORT}/graphql`);
62});

⚠️ Running into an error? If you're using the graphql-ws library, your specified subscription protocol must be consistent across your backend, frontend, and every other tool you use (including