Apollo Client is a library that helps you manage data in frontend applications built on top of well-known and respected Redux (predictable state container). If you were to start using it today, would you know how best to approach it?
GraphQL is a query language for API. It’s quite a new technology. For this reason, there are a lot of new libraries that try to resolve the most common use cases and help developers focus on the business logic. Apollo Client focuses on reducing the boilerplate to a minimum, yet it is fully functional and has support for great features such as server-side rendering, optimistic UI or support for WebSockets.
There are a lot of articles about how to tackle GraphQL on backend - if you’re interested in that topic, you can check out this guide.
As we’re only focusing on the frontend part of the application, we’ll use a server example provided by next.js. It’s built with a graphql-server package from Apollo Team but remember: the client is agnostic about the server implementation as long as it follows the GraphQL specs. The server is also configured to use hybrid WebSocket Transport. GraphQL specifies three operation types: queries (fetching data), mutations (modifying data) and subscriptions (pushing live updates to the client). Hybrid WS transport means that queries and mutations are going to be handled by regular HTTP interface and subscriptions are going to utilize WebSockets.
As for our frontend stack, we’ll base from next.js. However, we need to modify our config to support websockets:
import { ApolloClient, createNetworkInterface } from 'react-apollo'
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws';
let networkInterface = createNetworkInterface({
uri: 'http://localhost:8080/graphql',
opts: {
credentials: 'same-origin'
}
});
if(process.browser) {
const wsClient = new SubscriptionClient(`ws://localhost:5000/`, {
reconnect: true
});
networkInterface = addGraphQLSubscriptions(
networkInterface,
wsClient
);
}
function _initClient (headers, initialState) {
return new ApolloClient({
initialState,
ssrMode: !process.browser,
dataIdFromObject: result => result.id || null,
networkInterface: networkInterface,
})
}
We are injecting a WebSocket transfer protocol to our network interface handler so that the React Apollo knows how to deal with subscriptions. That’s all the configuration we need to start writing our business logic.
Subscriptions are getting more and more popular across modern web apps. It’s because users appreciate interactive interfaces and implementation has become easier than before. There is no longer a reason to go for hacky solutions like polling or manual re-fetching triggered by, for example, mouse movement. Since GraphQL abstracted away from the transport interface, we no longer face the problem where most of our API lived in REST controllers and WebSocket message handlers were built using custom implementations.
The HoC approach is very popular among React developers and it has been adapted by Apollo Client dev team. Let’s built a simple HoC that will inject messages (say: chat messages) to a component.
import { gql, graphql } from 'react-apollo';
const messagesQuery = gql`query { messages { id, content, user { nick } likes { nick } } }`;
export const withMessages = Component =>
graphql(messagesQuery)(Component);
That’s all we need to inject server data into a wrapped component. From within, we can consume it like a pro:
const { data: { messages, loading, error, refetch }, ...rest } = this.props;
The data is loaded whenever the component mounts. The possibility to re-fetch data is also injected automatically. We can even start polling in componentDidMount and stop it in componentWillUnmount react life-cycle methods. Apollo Client is even smart enough to fetch the data if more than one component utilizes the same part of data. Did I mention that the messages are already acquired on the server side?
This approach for handling data feels very similar to what react-redux can offer. In the end, react-apollo uses redux underneath.
We start from writing our subscription query that describes the data that we are willing to receive from the server:
const newMessagesSubscription = gql`subscription onMessageSent {
messageSent { id content user { nick } likes { nick } }
Now, we need to extend our withMessages HoC so that it injects subscription prop to our component:
const withMessages = Component =>
graphql(messagesQuery, {
props: props => {
return {
...props,
subscribeToNewMessages: params => {
return props.data.subscribeToMore({
document: newMessagesSubscription,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const newMessage = subscriptionData.data.messageSent;
return Object.assign({}, prev, {
messages: [...prev.messages, newMessage]
});
}
});
}
};
}
})(Component);
We’ve basically added a new method called subscribeToNewMessages that can be called from within our component. This method needs to meet a few constraints:
In our case, the updateQuery function adds consecutive messages to the messages’ object. Now we have to call subscribeToNewMessages() method from the component life-cycle method (componentWillMount preferably).
GraphQL itself is very powerful, but you may know that already. You might ask the question: “Why do I need Apollo Client to handle the data layer in my frontend application?” As I’ve shown above, it significantly reduces boilerplate, abstracts away from the transport protocol so that you could switch over to WebSockets, and greatly benefits from real-time data updates - all that in just about 15 lines of code. Just imagine implementing that with redux-saga on the frontend and Socket.IO/pusher on the backend. Have I convinced you now?