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 thegraphql-ws
library to add support for subscriptions to Apollo Server 4. We no longer recommend using the previously documentedsubscriptions-transport-ws
, because this library is not actively maintained. For more information about the differences between the two libraries, see Switching fromsubscriptions-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:
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:
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 theexpressMiddleware
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 listen
er.
Install
graphql-ws
,ws
, and@graphql-tools/schema
:Bash1npm install graphql-ws ws @graphql-tools/schema
Add the following imports to the file where you initialize your
ApolloServer
instance (we'll use these in later steps):TypeScriptindex.ts1import { 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';
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 Expressapp
to thecreateServer
function, which we imported from thehttp
module:TypeScriptindex.ts1// This `app` is the returned value from `express()`. 2const httpServer = createServer(app);
Create an instance of
GraphQLSchema
(if you haven't already).If you already pass the
schema
option to theApolloServer
constructor (instead oftypeDefs
andresolvers
), you can skip this step.The subscription server (which we'll instantiate next) doesn't take
typeDefs
andresolvers
options. Instead, it takes an executableGraphQLSchema
. We can pass thisschema
object to both the subscription server andApolloServer
. This way, we make sure that the same schema is being used in both places.TypeScriptindex.ts1const schema = makeExecutableSchema({ typeDefs, resolvers }); 2// ... 3const server = new ApolloServer({ 4 schema, 5});
Create a
WebSocketServer
to use as your subscription server.TypeScriptindex.ts1// 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);
Add plugins to your
ApolloServer
constructor to shutdown both the HTTP server and theWebSocketServer
:TypeScriptindex.ts1const 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});
Finally, ensure you are
listen
ing to yourhttpServer
.Most Express applications call
app.listen(...)
, but for your setup change this tohttpServer.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
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