Subscriptions
For the server implementation, you can take a look at this simple example.
Client setup
The GraphQL spec does not define a specific protocol for sending subscription requests. 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. The two libraries do not use the same WebSocket subprotocol, so you need to make sure that your server and clients all use the same library.
Apollo Client supports both graphql-ws and subscriptions-transport-ws. Apollo documentation suggest to use the newer library graphql-ws, but in case you need it, here its explained how to do it with both.
The new library: graphql-ws
Let's look at how to add support for this transport to Apollo Client using a link set up for newest library graphql-ws. First, install:
npm install graphql-ws
Then initialize a GraphQL web socket link:
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wsLink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/graphql",
})
);
We need to either use the GraphQLWsLink
or the HttpLink
depending on the operation type:
import { HttpLink, split } from "@apollo/client/core"
import { GraphQLWsLink } from "@apollo/client/link/subscriptions"; // <-- This one uses graphql-ws
import { getMainDefinition } from "@apollo/client/utilities"
// Create an http link:
const httpLink = new HttpLink({
uri: "http://localhost:3000/graphql"
})
// Create a GraphQLWsLink link:
const wsLink = new GraphQLWsLink(
createClient({
url: "ws://localhost:5000/",
})
);
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
)
},
wsLink,
httpLink
)
// Create the apollo client with cache implementation.
const apolloClient = new ApolloClient({
link,
cache: new InMemoryCache(),
});
The apollo client is the one that will be provided to the vue app, see the setup section for more details.
Now, queries and mutations will go over HTTP as normal, but subscriptions will be done over the websocket transport.
The old library: subscriptions-transport-ws
If you need to use subscriptions-transport-ws because your server still uses that protocol, instead of installing graphql-ws, install:
npm install subscriptions-transport-ws
And then initialize a GraphQL web socket link:
import { WebSocketLink } from "@apollo/client/link/ws" // <-- This one uses subscriptions-transport-ws
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000/`,
options: {
reconnect: true
}
})
The rest of the configuration (creating a httpLink and link) is the same as described above for graphql-ws.
Subscribe To More
If you need to update a smart query result from a subscription, the best way is using the subscribeToMore
smart query method. It will create Smart Subscriptions that are linked to the smart query. Just add a subscribeToMore
to your smart query:
apollo: {
tags: {
query: TAGS_QUERY,
subscribeToMore: {
document: gql`subscription name($param: String!) {
itemAdded(param: $param) {
id
label
}
}`,
// Variables passed to the subscription. Since we're using a function,
// they are reactive
variables () {
return {
param: this.param,
}
},
// Mutate the previous result
updateQuery: (previousResult, { subscriptionData }) => {
// Here, return the new result from the previous with the new data
},
}
}
}
TIP
Note that you can pass an array of subscriptions to subscribeToMore
to subscribe to multiple subscriptions on this query.
Alternate usage
You can access the queries you defined in the apollo
option with this.$apollo.queries.<name>
, so it would look like this:
this.$apollo.queries.tags.subscribeToMore({
// GraphQL document
document: gql`subscription name($param: String!) {
itemAdded(param: $param) {
id
label
}
}`,
// Variables passed to the subscription
variables: {
param: '42',
},
// Mutate the previous result
updateQuery: (previousResult, { subscriptionData }) => {
// Here, return the new result from the previous with the new data
},
})
If the related query is stopped, the subscription will be automatically destroyed.
Here is an example:
// Subscription GraphQL document
const TAG_ADDED = gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`
// SubscribeToMore tags
// We have different types of tags
// with one subscription 'channel' for each type
this.$watch(() => this.type, (type, oldType) => {
if (type !== oldType || !this.tagsSub) {
// We need to unsubscribe before re-subscribing
if (this.tagsSub) {
this.tagsSub.unsubscribe()
}
// Subscribe on the query
this.tagsSub = this.$apollo.queries.tags.subscribeToMore({
document: TAG_ADDED,
variables: {
type,
},
// Mutate the previous result
updateQuery: (previousResult, { subscriptionData }) => {
// If we added the tag already don't do anything
// This can be caused by the `updateQuery` of our addTag mutation
if (previousResult.tags.find(tag => tag.id === subscriptionData.data.tagAdded.id)) {
return previousResult
}
return {
tags: [
...previousResult.tags,
// Add the new tag
subscriptionData.data.tagAdded,
],
}
},
})
}
}, {
immediate: true,
})
Simple subscription
DANGER
If you want to update a query with the result of the subscription, use subscribeToMore
. The methods below are suitable for a 'notify' use case.
You can declare Smart Subscriptions in the apollo
option with the $subscribe
keyword:
apollo: {
// Subscriptions
$subscribe: {
// When a tag is added
tagAdded: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// Reactive variables
variables () {
// This works just like regular queries
// and will re-subscribe with the right variables
// each time the values change
return {
type: this.type,
}
},
// Result hook
// Don't forget to destructure `data`
result ({ data }) {
console.log(data.tagAdded)
},
},
},
},
You can then access the subscription with this.$apollo.subscriptions.<name>
.
TIP
Just like for queries, you can declare the subscription with a function, and you can declare the query
option with a reactive function.
When server supports live queries and uses subscriptions to update them, like Hasura, you can use simple subscriptions for reactive queries:
data () {
return {
tags: [],
};
},
apollo: {
$subscribe: {
tags: {
query: gql`subscription {
tags {
id
label
type
}
}`,
result ({ data }) {
this.tags = data.tags;
},
},
},
},
Skipping the subscription
If the subscription is skipped, it will disable it and it will not be updated anymore. You can use the skip
option:
// Apollo-specific options
apollo: {
// Subscriptions
$subscribe: {
// When a tag is added
tags: {
query: gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`,
// Reactive variables
variables () {
return {
type: this.type,
}
},
// Result hook
result (data) {
// Let's update the local data
this.tags.push(data.tagAdded)
},
// Skip the subscription
skip () {
return this.skipSubscription
}
},
},
},
Here, skip
will be called automatically when the skipSubscription
component property changes.
You can also access the subscription directly and set the skip
property:
this.$apollo.subscriptions.tags.skip = true
Manually adding a smart Subscription
You can manually add a smart subscription with the $apollo.addSmartSubscription(key, options)
method:
created () {
this.$apollo.addSmartSubscription('tagAdded', {
// Same options like '$subscribe' above
})
}
TIP
Internally, this method is called for each entry of the $subscribe
object in the component apollo
option.
Standard Apollo subscribe
Use the $apollo.subscribe()
method to subscribe to a GraphQL subscription that will get killed automatically when the component is destroyed. It will NOT create a Smart Subscription.
mounted () {
const subQuery = gql`subscription tags($type: String!) {
tagAdded(type: $type) {
id
label
type
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables: {
type: 'City',
},
})
observer.subscribe({
next (data) {
console.log(data)
},
error (error) {
console.error(error)
},
})
},