Across our last four articles in this series, we explored many GraphQL-related concepts.
We went from designing GraphQL schema to adding authentication and authorization. We integrated Apollo Client which allows the app to send authenticated GraphQL requests to the server. Finally, with Apollo in place, we were able to fetch necessary data to render a feed, showing posts from users someone is following.
In this article, we’re going to learn about GraphQL subscriptions. Since Dgraph’s GraphQL API has this feature built-in; it’s really simple to use them within your app.
Here are the list of topics for today:
If you want, you can take a look at the previous articles in this series that follow up to this one:
You can find all the code changes for today’s article here.
Subscriptions are what make a GraphQL API capable of serving real-time data to its clients. An application that uses GraphQL subscriptions enables it to have it’s data updated without the need for polling for changes in the data. A subscription works simply: describe the data you want the client to subscribe to and when that data changes, the server will send the updates to the client automatically.
Since it’s GraphQL, the client will of course define what data it wants from the server through a subscription query.
For subscriptions to work, there needs to be a persistent connection between the client and the server. This is usually via WebSocket, as we’ll soon see when we get to the code. This is in contrast with normal GraphQL queries we’ve seen so far. With normal GraphQL queries, you just send a request whenever you need to.
Subscriptions are also autonomous, depending on events unlike normal queries. When these events occur, the server will then execute the subscription query. It won’t dispatch the query without these events.
For example, consider an app that tracks the number of daily COVID cases. This type of data is constantly changing, so you need a way to make sure that your app (the client) always has the latest information. You’d “subscribe” to the relevant databases across the world. In the event of new records, the servers will push the latest data your client’s way and your app will render the latest COVID statistics.
To know more about subscriptions, check out our subscription docs and our blog post on how GraphQL subscriptions work by building a NextJS app. You’ll get more hands-on familiarity with it as you go along with me today.
As I said earlier, subscriptions make use of WebSockets for communication. Subscriptions maintain a persistent, stateful connection between the client and the server. The client doesn’t immediately request anything and the server doesn’t respond either. The server only pushes data when an event occurs that the client explicitly specified via a subscription query. For that, the server needs to know the subscription query and maintain a steady connection with the client.
Unlike HTTP, WebSockets offer a bi-directional way of communication between two entities. It’s low overhead and scalable when it comes to real-time data transfer. WebSockets, by design, are suitable for maintaining a persistent co
In order to enable subscription, you need to make some changes to both your server and client side code and configuration.
In Dgraph, you can enable subscription on any of your types by just appending the @withSubscription
directive to it. Notice the following where we attach it to our User type definition:
type User @withSubscription
...
}
That’s all it takes to make the GraphQL API subscription-ready in Dgraph. Re-deploy your backend so that the changes can take effect. Now your endpoint can process subscription queries and send back appropriate responses to the client.
We’re using Apollo as the GraphQL client of choice. Since it’s in charge of sending queries to the server, you need to configure it so that it can handle subscription queries too. Apollo has tools that you can use for that.
It all starts with installing the following package:
npm install subscriptions-transport-ws
In the previous article, we wrote two separate “links” for taking care of authentication headers and HTTP connection. Then we chained them together. With this library installed, we can do the same thing for handling WebSocket connection with the server.
In your src/Components/ApolloWrapper.js
file, import the following definitions:
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
import { split } from '@apollo/client';
Now write a new WebSocketLink in the following way:
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_BACKEND_ENDPOINT.replace("https://", "wss://"),
options: {
reconnect: true,
minTimeout: 30000,
connectionParams: {
"X-Auth-Token": xAuthToken.__raw,
},
},
});
What’s happening here?
uri
parameter. Notice the protocol wss
, which is the protocol for WebSocket URL.connectionParams
.At this point, we have three links handling three different things. For GraphQL operations other than subscriptions, we should be using HTTP. That’s what we’ve been doing until now. When we create our client instance, we concatenate our httpLink
with the authLink
, for authenticated HTTP requests to the server.
For subscriptions, we need to tell Apollo to use WebSockets instead. We’ve already configured wsLink
so that it has the authentication header.
So how do we proceed?
Apollo offers a tool split
that we can use in this situation. It allows us to use specific “links” based on conditions.
In your ApolloWrapper.js
file, put the following bit of code:
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
What’s happening here?
getMainDefinition
function, we’re checking whether the kind of operation is subscriptions or not.What’s left is to just use splitLink
that we just defined above as the link
parameter in the ApolloClient
constructor:
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
});
After making all these changes, your ApolloWrapper.js
file should look like this:
import {
ApolloClient,
ApolloProvider,
InMemoryCache,
createHttpLink,
split,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { useAuth0 } from "@auth0/auth0-react";
import { useState, useEffect } from "react";
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from "@apollo/client/utilities";
import App from "../App";
const ApolloWrapper = () => {
const { isAuthenticated, getIdTokenClaims } = useAuth0();
const [xAuthToken, setXAuthToken] = useState("");
useEffect(() => {
const getToken = async () => {
const token = isAuthenticated ? await getIdTokenClaims() : "";
setXAuthToken(token);
};
getToken();
}, [getIdTokenClaims, isAuthenticated]);
const httpLink = createHttpLink({
uri: process.env.REACT_APP_BACKEND_ENDPOINT,
});
const authLink = setContext((_, { headers, ...rest }) => {
if (!xAuthToken) return { headers, ...rest };
return {
...rest,
headers: {
...headers,
"X-Auth-Token": xAuthToken.__raw,
},
};
});
const wsLink = new WebSocketLink({
uri: process.env.REACT_APP_BACKEND_ENDPOINT.replace("https://", "wss://"),
options: {
reconnect: true,
minTimeout: 30000,
connectionParams: {
"X-Auth-Token": xAuthToken.__raw,
},
},
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
});
return (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
};
export default ApolloWrapper;
Next thing to do on the client side is to change the query we had before to a subscription query.
Open your src/GraphQL/Queries/Queries.js
file and just replace the query keyword with the subscription keyword:
export const QUERY_LOGGED_IN_USER = gql`
subscription getUserInfo($userFilter: UserFilter!) {
queryUser(filter: $userFilter) {
email
following {
username
avatarImageURL
posts {
id
imageURL
description
likes
postedBy {
username
avatarImageURL
}
}
}
}
}
`;
Since this is a subscription query, we can’t use the useQuery
hook for it. We’ll need to use the useSubscription
hook instead. This works the same way as useQuery
and returns three properties: data
, loading
, and error
.
So in your src/Components/Feed.js
file, import the useQuery
hook:
import { useSubscription } from "@apollo/client";
And place it where you previously called the useQuery
hook:
const { data, loading, error } = useSubscription(QUERY_LOGGED_IN_USER, {
variables: {
userFilter: {
email: {
eq: email,
},
},
},
});
Now run npm start
from your terminal. Your app should render like before.
The only difference is that now if any piece of data related to the subscription query changes on your backend, it’ll push those updates to the React app. As a result, the UI will update with the newest information in real-time, without the need to refresh your browser or restart the app. Let’s see that in action.
Time to test out the subscription feature we just added. We’ll just change some data on the server from the Dgraph Cloud dashboard and see if the app reflects those changes.
In order to do that, you’ll need to log into felix
’s account from the app, get the id_token
, and use it as the X-Auth-Token
header in the request. So follow the steps below:
npm start
from the terminal.felix
. So log out if you’re logged in from another email.id_token
field.id_token
as the value of X-Auth-Token
header.Go back to your app and log back in to the other account that was following felix
. You should see posts from felix
rendered again.
We’re ready to alter some data.
First we need to know the id
s of felix
’s posts. The update
mutation will require them to filter out which posts need updating.
So fire the following query from GraphQL window first:
query {
queryPost {
id
description
}
}
Now execute the following mutation that changes the description of one of felix
’s posts:
mutation UpdateDescription($updateData: UpdatePostInput!) {
updatePost(input: $updateData) {
post {
id
description
likes
}
}
}
With the following value for $updateData
variable:
{
"updateData": {
"filter": {
"id": "0x60563948"
},
"set": {
"description": "The beach was beautiful!"
}
}
}
You should see your app rendering the latest data as soon as you execute the mutation:
Today, we learned about GraphQL subscriptions. Subscriptions enable real-time updates for applications that need to serve the latest data to its users. Unlike normal queries, servers respond to subscription queries when the related data changes. So a lightweight persistent connection is established between the server and the client using WebSockets.
Enabling subscriptions in Dgraph is really simple. Continuing our exploration of different concepts throughout the Instagram clone series, today we played around with subscriptions by incorporating it into our app. We learned how to set up the client side using tools from the Apollo library. We kept various concerns separate by using “Apollo links”. We also learned how to establish authenticated WebSocket connections.
Stay in the loop as we explore more stuff in upcoming content!
Below are some relevant resources that you might find useful: