Subscriptions
Get real-time updates from your GraphQL server
In addition to queries and mutations, GraphQL supports a third operation type: subscriptions.
Like queries, subscriptions enable you to fetch data. Unlike queries, subscriptions are long-lasting operations that can change their result over time. They can maintain an active connection to your GraphQL server (most commonly via WebSocket), enabling the server to push updates to the subscription's result.
Subscriptions are useful for notifying your client in real time about changes to back-end data, such as the creation of a new object or updates to an important field.
When to use subscriptions
In the majority of cases, your client should not use subscriptions to stay up to date with your backend. Instead, you should poll intermittently with queries, or re-execute queries on demand when a user performs a relevant action (such as clicking a button).
You should use subscriptions for the following:
Small, incremental changes to large objects. Repeatedly polling for a large object is expensive, especially when most of the object's fields rarely change. Instead, you can fetch the object's initial state with a query, and your server can proactively push updates to individual fields as they occur.
Low-latency, real-time updates. For example, a chat application's client wants to receive new messages as soon as they're available.
Note: Subscriptions cannot be used to listen to local client events, like subscribing to changes in the cache. Subscriptions are intended to be used to subscribe to external data changes, and have those received changes be stored in the cache. You can then leverage Apollo Client's observability model to watch for changes in the cache, using client.watchQuery
or useQuery
.
Supported subscription protocols
The GraphQL spec does not define a specific protocol for sending subscription requests. Apollo Client supports the following protocols for subscriptions:
- WebSocket, using one of the following subprotocols:
graphql-ws
subscriptions-transport-ws
(⚠️ unmaintained)
- HTTP, using chunked multipart responses (Apollo Client
3.7.11
and later)
You must use the same protocol as the GraphQL endpoint you're communicating with.
WebSocket subprotocols
The first popular JavaScript library to implement subscriptions over WebSocket is called subscriptions-transport-ws
. This library is no longer actively maintained. Its successor is a library called graphql-ws
. These two libraries do not use the same WebSocket subprotocol, so you need to use the same subprotocol that your GraphQL endpoint uses.
The WebSocket setup section below uses graphql-ws
. If your endpoint uses subscriptions-transport-ws
, see this section for differences in configuration.
Note: Confusingly, the subscriptions-transport-ws
library calls its WebSocket subprotocol graphql-ws
, and the graphql-ws
library calls its subprotocol graphql-transport-ws
! In this article, we refer to the two libraries (subscriptions-transport-ws
and graphql-ws
), not the two subprotocols.
HTTP
To use Apollo Client with a GraphQL endpoint that supports multipart subscriptions over HTTP, make sure you're using version 3.7.11
or later.
Aside from updating your client version, no additional configuration is required! Apollo Client automatically sends the required headers with the request if the terminating HTTPLink
is passed a subscription operation.
Usage with Relay or urql
To consume a multipart subscription over HTTP in an app using Relay or urql, Apollo Client provides network layer adapters that handle the parsing of the multipart response format.
Relay
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/relay';import { Environment, Network, RecordSource, Store } from 'relay-runtime';const fetchMultipartSubs = createFetchMultipartSubscription('https://api.example.com');const network = Network.create(fetchQuery, fetchMultipartSubs);export const RelayEnvironment = new Environment({network,store: new Store(new RecordSource()),});
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/relay';import { Environment, Network, RecordSource, Store } from 'relay-runtime';const fetchMultipartSubs = createFetchMultipartSubscription('https://api.example.com');const network = Network.create(fetchQuery, fetchMultipartSubs);export const RelayEnvironment = new Environment({network,store: new Store(new RecordSource()),});
urql
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/urql';import { Client, fetchExchange, subscriptionExchange } from '@urql/core';const url = 'https://api.example.com';const multipartSubscriptionForwarder = createFetchMultipartSubscription(url);const client = new Client({url,exchanges: [fetchExchange,subscriptionExchange({forwardSubscription: multipartSubscriptionForwarder,}),],});
import { createFetchMultipartSubscription } from '@apollo/client/utilities/subscriptions/urql';import { Client, fetchExchange, subscriptionExchange } from '@urql/core';const url = 'https://api.example.com';const multipartSubscriptionForwarder = createFetchMultipartSubscription(url);const client = new Client({url,exchanges: [fetchExchange,subscriptionExchange({forwardSubscription: multipartSubscriptionForwarder,}),],});
Defining a subscription
You define a subscription on both the server side and the client side, just like you do for queries and mutations.
Server side
You define available subscriptions in your GraphQL schema as fields of the Subscription
type. The following commentAdded
subscription notifies a subscribing client whenever a new comment is added to a particular blog post (specified by postID
):
type Subscription {commentAdded(postID: ID!): Comment}
For more information on implementing support for subscriptions on the server side, see the Apollo Server documentation for subscriptions.
Client side
In your application's client, you define the shape of each subscription you want Apollo Client to execute, like so:
const COMMENTS_SUBSCRIPTION: TypedDocumentNode<OnCommentAddedSubscription,OnCommentAddedSubscriptionVariables> = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;
const COMMENTS_SUBSCRIPTION = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;
When Apollo Client executes the OnCommentAdded
subscription, it establishes a connection to your GraphQL server and listens for response data. Unlike with a query, there is no expectation that the server will immediately process and return a response. Instead, your server only pushes data to your client when a particular event occurs on your backend.
Whenever your GraphQL server does push data to a subscribing client, that data conforms to the structure of the executed subscription, just like it does for a query:
{"data": {"commentAdded": {"id": "123","content": "What a thoughtful and well written post!"}}}
WebSocket setup
1. Install required libraries
Apollo Link is a library that helps you customize Apollo Client's network communication. You can use it to define a link chain that modifies your operations and routes them to the appropriate destination.
To execute subscriptions over WebSocket, you can add a GraphQLWsLink
to your link chain. This link requires the graphql-ws
library. Install it like so:
npm install graphql-ws
2. Initialize a GraphQLWsLink
Import and initialize a GraphQLWsLink
object in the same project file where you initialize ApolloClient
:
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',}),);
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',}),);
Replace the value of the url
option with your GraphQL server's subscription-specific WebSocket endpoint. If you're using Apollo Server, see Setting a subscription endpoint.
3. Split communication by operation (recommended)
Although Apollo Client can use your GraphQLWsLink
to execute all operation types, in most cases it should continue using HTTP for queries and mutations. This is because queries and mutations don't require a stateful or long-lasting connection, making HTTP more efficient and scalable if a WebSocket connection isn't already present.
To support this, the @apollo/client
library provides a split
function that lets you use one of two different Link
s, according to the result of a boolean check.
The following example expands on the previous one by initializing both a GraphQLWsLink
and an HttpLink
. It then uses the split
function to combine those two Link
s into a single Link
that uses one or the other according to the type of operation being executed.
import { split, HttpLink } from '@apollo/client';import { getMainDefinition } from '@apollo/client/utilities';import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const httpLink = new HttpLink({uri: 'http://localhost:4000/graphql',});const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',}),);// The split function takes three parameters://// * A function that's called for each operation to execute// * The Link to use for an operation if the function returns a "truthy" value// * The Link to use for an operation if the function returns a "falsy" valueconst splitLink = split(({ query }) => {const definition = getMainDefinition(query);return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';},wsLink,httpLink,);
import { split, HttpLink } from '@apollo/client';import { getMainDefinition } from '@apollo/client/utilities';import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const httpLink = new HttpLink({uri: 'http://localhost:4000/graphql',});const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',}),);// The split function takes three parameters://// * A function that's called for each operation to execute// * The Link to use for an operation if the function returns a "truthy" value// * The Link to use for an operation if the function returns a "falsy" valueconst splitLink = split(({ query }) => {const definition = getMainDefinition(query);return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';},wsLink,httpLink,);
Using this logic, queries and mutations will use HTTP as normal, and subscriptions will use WebSocket.
4. Provide the link chain to Apollo Client
After you define your link chain, you provide it to Apollo Client via the link
constructor option:
import { ApolloClient, InMemoryCache } from '@apollo/client';// ...code from the above example goes here...const client = new ApolloClient({link: splitLink,cache: new InMemoryCache()});
import { ApolloClient, InMemoryCache } from '@apollo/client';// ...code from the above example goes here...const client = new ApolloClient({link: splitLink,cache: new InMemoryCache()});
If you provide the link
option, it takes precedence over the uri
option (uri
sets up a default HTTP link chain using the provided URL).
5. Authenticate over WebSocket (optional)
It is often necessary to authenticate a client before allowing it to receive subscription results. To do this, you can provide a connectionParams
option to the GraphQLWsLink
constructor, like so:
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',connectionParams: {authToken: user.authToken,},}));
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';import { createClient } from 'graphql-ws';const wsLink = new GraphQLWsLink(createClient({url: 'ws://localhost:4000/subscriptions',connectionParams: {authToken: user.authToken,},}));
Your GraphQLWsLink
passes the connectionParams
object to your server whenever it connects. Your server receives the connectionParams
object and can use it to perform authentication, along with any other connection-related tasks.
Subscriptions via multipart HTTP
No additional libraries or configuration are required. Apollo Client adds the required headers to your request when the default terminating HTTPLink
receives a subscription operation at the uri
specified when initializing the link or Apollo Client instance.
Note: in order to use subscriptions over multipart HTTP in a React Native application, additional configuration is required. See the React Native docs for more information.
Executing a subscription
You use Apollo Client's useSubscription
Hook to execute a subscription from React. Like useQuery
, useSubscription
returns an object from Apollo Client that contains loading
, error
, and data
properties you can use to render your UI.
The following example component uses the subscription we defined earlier to render the most recent comment that's been added to a specified blog post. Whenever the GraphQL server pushes a new comment to the client, the component re-renders with the new comment.
const COMMENTS_SUBSCRIPTION: TypedDocumentNode<OnCommentAddedSubscription,OnCommentAddedSubscriptionVariables> = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;function LatestComment({ postID }: LatestCommentProps) {const { data, loading } = useSubscription(COMMENTS_SUBSCRIPTION,{ variables: { postID } });return <h4>New comment: {!loading && data.commentAdded.content}</h4>;}
const COMMENTS_SUBSCRIPTION = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;function LatestComment({ postID }) {const { data, loading } = useSubscription(COMMENTS_SUBSCRIPTION,{ variables: { postID } });return <h4>New comment: {!loading && data.commentAdded.content}</h4>;}
Subscribing to updates for a query
Whenever a query returns a result in Apollo Client, that result includes a subscribeToMore
function. You can use this function to execute a followup subscription that pushes updates to the query's original result.
The subscribeToMore
function is similar in structure to the fetchMore
function that's commonly used for handling pagination. The primary difference is that fetchMore
executes a followup query, whereas subscribeToMore
executes a subscription.
As an example, let's start with a standard query that fetches all of the existing comments for a given blog post:
const COMMENTS_QUERY: TypedDocumentNode<CommentsForPostQuery,CommentsForPostQueryVariables> = gql`query CommentsForPost($postID: ID!) {post(postID: $postID) {comments {idcontent}}}`;function CommentsPageWithData({ params }: CommentsPageWithDataProps) {const result = useQuery(COMMENTS_QUERY,{ variables: { postID: params.postID } });return <CommentsPage {...result} />;}
const COMMENTS_QUERY = gql`query CommentsForPost($postID: ID!) {post(postID: $postID) {comments {idcontent}}}`;function CommentsPageWithData({ params }) {const result = useQuery(COMMENTS_QUERY,{ variables: { postID: params.postID } });return <CommentsPage {...result} />;}
Let's say we want our GraphQL server to push an update to our client as soon as a new comment is added to the post. First we need to define the subscription that Apollo Client will execute when the COMMENTS_QUERY
returns:
const COMMENTS_SUBSCRIPTION: TypedDocumentNode<OnCommentAddedSubscription,OnCommentAddedSubscriptionVariables> = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;
const COMMENTS_SUBSCRIPTION = gql`subscription OnCommentAdded($postID: ID!) {commentAdded(postID: $postID) {idcontent}}`;
Next, we modify our CommentsPageWithData
function to add a subscribeToNewComments
property to the CommentsPage
component it returns. This property is a function that will be responsible for calling subscribeToMore
after the component mounts.
function CommentsPageWithData({ params }: CommentsPageWithDataProps) {const { subscribeToMore, ...result } = useQuery(COMMENTS_QUERY,{ variables: { postID: params.postID } });return (<CommentsPage{...result}subscribeToNewComments={() =>subscribeToMore({document: COMMENTS_SUBSCRIPTION,variables: { postID: params.postID },updateQuery: (prev, { subscriptionData }) => {if (!subscriptionData.data) return prev;const newFeedItem = subscriptionData.data.commentAdded;return Object.assign({}, prev, {post: {comments: [newFeedItem, ...prev.post.comments]}});}})}/>);}
function CommentsPageWithData({ params }) {const { subscribeToMore, ...result } = useQuery(COMMENTS_QUERY,{ variables: { postID: params.postID } });return (<CommentsPage{...result}subscribeToNewComments={() =>subscribeToMore({document: COMMENTS_SUBSCRIPTION,variables: { postID: params.postID },updateQuery: (prev, { subscriptionData }) => {if (!subscriptionData.data) return prev;const newFeedItem = subscriptionData.data.commentAdded;return Object.assign({}, prev, {post: {comments: [newFeedItem, ...prev.post.comments]}});}})}/>);}
In the example above, we pass three options to subscribeToMore
:
document
indicates the subscription to execute.variables
indicates the variables to include when executing the subscription.updateQuery
is a function that tells Apollo Client how to combine the query's currently cached result (prev
) with thesubscriptionData
that's pushed by our GraphQL server. The return value of this function completely replaces the current cached result for the query.
Finally, in our definition of CommentsPage
, we tell the component to subscribeToNewComments
when it mounts:
export function CommentsPage({ subscribeToNewComments }: CommentsPageProps) {useEffect(() => subscribeToNewComments(), []);return <>...</>}
export function CommentsPage({ subscribeToNewComments }) {useEffect(() => subscribeToNewComments(), []);return <>...</>}
useSubscription
API reference
Note: If you're using React Apollo's Subscription
render prop component, the option/result details listed below are still valid (options are component props and results are passed into the render prop function). The only difference is that a subscription
prop (which holds a GraphQL subscription document parsed into an AST by gql
) is also required.
Options
The useSubscription
Hook accepts the following options:
ApolloClient<object>
An ApolloClient
instance. By default useSubscription
/ Subscription
uses the client passed down via context, but a different client can be passed in.
DefaultContext
Shared context between your component and your network interface (Apollo Link).
ErrorPolicy
Specifies the ErrorPolicy
to be used for this operation
Record<string, any>
Shared context between your component and your network interface (Apollo Link).
FetchPolicy
How you want your component to interact with the Apollo cache. For details, see Setting a fetch policy.
boolean
If true
, the hook will not cause the component to rerender. This is useful when you want to control the rendering of your component yourself with logic in the onData
and onError
callbacks.
Changing this to true
when the hook already has data
will reset the data
to undefined
.
() => void
Allows the registration of a callback function that will be triggered each time the useSubscription
Hook / Subscription
component completes the subscription.
(options: OnDataOptions<TData>) => any
Allows the registration of a callback function that will be triggered each time the useSubscription
Hook / Subscription
component receives data. The callback options
object param consists of the current Apollo Client instance in client
, and the received subscription data in data
.
(error: ApolloError) => void
Allows the registration of a callback function that will be triggered each time the useSubscription
Hook / Subscription
component receives an error.
boolean | ((options: BaseSubscriptionOptions<TData, TVariables>) => boolean)
Determines if your subscription should be unsubscribed and subscribed again when an input to the hook (such as subscription
or variables
) changes.
boolean
Determines if the current subscription should be skipped. Useful if, for example, variables depend on previous queries and are not ready yet.
TVariables
An object containing all of the variables your subscription needs to execute
() => void
⚠️ Deprecated
Use onComplete
instead
Allows the registration of a callback function that will be triggered when the useSubscription
Hook / Subscription
component completes the subscription.
(options: OnSubscriptionDataOptions<TData>) => any
⚠️ Deprecated
Use onData
instead
Allows the registration of a callback function that will be triggered each time the useSubscription
Hook / Subscription
component receives data. The callback options
object param consists of the current Apollo Client instance in client
, and the received subscription data in subscriptionData
.
Result
After being called, the useSubscription
Hook returns a result object with the following properties:
TData
An object containing the result of your GraphQL subscription. Defaults to an empty object.
ApolloError
A runtime error with graphQLErrors
and networkError
properties
boolean
A boolean that indicates whether any initial data has been returned
The older subscriptions-transport-ws
library
If your server uses subscriptions-transport-ws
instead of the newer graphql-ws
library, you need to make a few changes to how you set up your link:
Instead of
npm install graphql-ws
:npm install subscriptions-transport-wsInstead of
import { createClient } from 'graphql-ws'
:import { SubscriptionClient } from 'subscriptions-transport-ws'Instead of
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
:import { WebSocketLink } from '@apollo/client/link/ws'The options you pass to
new SubscriptionClient
differ slightly from those passed tocreateClient
:- The first argument passed to the
SubscriptionClient
constructor is the URL for your subscription server. - The
connectionParams
option is nested under an options object calledoptions
instead of being at the top level. (You can also pass thenew SubscriptionClient
constructor arguments directly tonew WebSocketLink
.) - See the
subscriptions-transport-ws
README for completeSubscriptionClient
API docs.
- The first argument passed to the
After you create your wsLink
, everything else in this article still applies: useSubscription
, subscribeToMore
, and split links work exactly the same way for both implementations.
The following is an example of a typical WebSocketLink
initialization:
import { WebSocketLink } from '@apollo/client/link/ws';import { SubscriptionClient } from 'subscriptions-transport-ws';const wsLink = new WebSocketLink(new SubscriptionClient('ws://localhost:4000/subscriptions', {connectionParams: {authToken: user.authToken,},}),);
import { WebSocketLink } from '@apollo/client/link/ws';import { SubscriptionClient } from 'subscriptions-transport-ws';const wsLink = new WebSocketLink(new SubscriptionClient('ws://localhost:4000/subscriptions', {connectionParams: {authToken: user.authToken,},}),);
More details on WebSocketLink
's API can be found in its API docs.