Dive Into GraphQL Part IV: Building a GraphQL-powered mobile application With React

Gildas GarciaFrançois Zaninotto
#graphql#react

After explaining how to develop a robust GraphQL server in practice with the third post in the Dive Into GraphQL Series, it's time to deal with the client side.

How to develop a Twitter-like single page app on top of a GraphQL API? Actually, it's easier than doing the same with a REST API. But since GraphQL opens new possibilities (caching, optimistic UI, batch queries), this post is longer than what you could expect - it will take one hour of your time.

I gathered there all the lessons learned by the marmelab developers when developing real life GraphQL clients, and our impressions on the developer experience. By the end of this tutorial, you'll be able to write a production-grade web app powered by GraphQL, and you'll know if you like it!

Here is what I'll be talking about:

The Target

Building up on the Twitter example, I'll reuse the GraphQL server developed in the previous post. My objective is to develop a mobile web app looking very much like the Twitter application itself:

Mobile Twitter

I'll use ES6 and React.js, the tools we use for all our frontend apps at marmelab.

Note: Most of this tutorial is also relevant if you use React Native.

As a starting point, I'll use the empty shell created by the create-react-app command, which configures webpack and Babel, adds DevTools support, and hot module reloading.

yarn create react-app client
cd client
npm start
Raw CRA

My browser shows the familiar React app placeholder, which I'm going to transform into Twitter. Ready?

Creating The UI

Based on the mockup, I divide the home page by zones and responsibilities. This defines the tree structure of components I need to develop. To display the list of tweets, I must therefore create:

  • a <Header> component showing the current user avatar, a title and a New tweet button,
  • a <Tweet> component showing the tweet author, body and stats, and
  • an <App> component for the list of tweets.

I chose Material-UI in its upcoming 1.0 version to quickly implement the UI. Material UI builds up on the Material Design concepts, and provides a library of ready-to-use React components.

yarn add material-ui@next material-ui-icons

Here is the Header component, showing the current user avatar. Nothing particular here, it's a simple React component:

// in Header.js
import React from 'react';
import { AppBar, Avatar, IconButton, ModeEditIcon, Toolbar, Typography } from 'material-ui';

const Header = ({ currentUser }) => (
    <AppBar position="static" color="default">
        <Toolbar>
            {currentUser &&
                <Avatar alt={currentUser.full_name} src={currentUser.avatar_url} />
            }
            <Typography type="title" color="inherit">
                Home
            </Typography>
            <IconButton>
                <ModeEditIcon />
            </IconButton>
        </Toolbar>
    </AppBar>
);

export default Header;

Note: I omitted propTypes and style related code in those excerpts for brevity. Refer to the tutorial companion repository marmelab/GraphQL-example to see the complete code.

The Tweet component is a bit longer, but still very simple:

// in Tweet.js
import React from 'react';
import { Avatar, Card, CardContent, Typography } from 'material-ui';
import CachedIcon from 'material-ui-icons/Cached';
import FavoriteBorderIcon from 'material-ui-icons/FavoriteBorder';
import ChatBubbleOutlineIcon from 'material-ui-icons/ChatBubbleOutline';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';

const Tweet = ({ tweet }) => (
    <Card>
        <CardContent>
            <Avatar alt={tweet.Author.full_name} src={tweet.Author.avatar_url} />
            <div>
                <span>{tweet.Author.full_name}</span>
                <span>{tweet.Author.username}</span>
                <span> - </span>
                <span>{distanceInWordsToNow(tweet.date)}</span>
                <Typography component="p">{tweet.body}</Typography>
                <div>
                    <div>
                        <ChatBubbleOutlineIcon />
                        {tweet.Stats.responses}
                    </div>
                    <div>
                        <CachedIcon />
                        {tweet.Stats.retweets}
                    </div>
                    <div>
                        <FavoriteBorderIcon />
                        {tweet.Stats.likes}
                    </div>
                </div>
            </div>
        </CardContent>
    </Card>
);

export default Tweet;

You may have noticed the distanceInWordsToNow() function. It comes from the date-fns package, a fantastic library for manipulating dates in a functional way. It's more modular than moment.js, and immutable by default - which plays well with React.

Finally, I implemented the list of tweets in the App component:

// in App.js
import React from 'react';

import Header from './Header';
import Tweet from './Tweet';

const App = ({ currentUser, tweets } }) =>(
    <div>
        <Header currentUser={currentUser} />
        {tweets.map(tweet =>
            <Tweet key={tweet.id} tweet={tweet} />
        )}
    </div>
);

App.defaultProps = {
    currentUser: { id: 'user1', username: 'johndoe', full_name: 'John Doe', avatar_url: 'https://material-ui-1dab0.firebaseapp.com/build/fa158bc2d4774f4ae14cbbec7730af23.jpg' },
    tweets: [
        {
            id: 'tweet1',
            body: 'Lorem Ipsum',
            date: new Date(),
            Author: { id: 'author1', username: 'johndoe', full_name: 'John Doe', avatar_url: 'https://material-ui-1dab0.firebaseapp.com/build/fa158bc2d4774f4ae14cbbec7730af23.jpg' },
            Stats: { views: 98, likes: 42, retweets: 218, responses: 7 },
        },
        {
            id: 'tweet2',
            body: 'Sic dolor amet',
            date: new Date(),
            Author: { id: 'author2', username: 'janedoe', full_name: 'Jane Doe', avatar_url: 'https://material-ui-1dab0.firebaseapp.com/build/b16427bb030d63fd8e52ea84defda1d1.jpg' },
            Stats: { views: 98, likes: 42, retweets: 218, responses: 7 },
        },
    ]
}

export default App;

I use the defaultProps to populate the components with mock data while building the user interface. That way, I can adjust the components style without waiting for the server. When it's time to plug the components with a GraphQL data provider, I'll remove these default props.

The application

Now that the UI is ready, it's time to plug the app to the GraphQL server.

Choosing a GraphQL Client

In order to communicate with the GraphQL server, I can use either one of the three following solutions:

Using fetch means that I would have to write a lot of boilerplate code by myself to handle optimistic response, caching, etc. I would probably do it wrong, and it's a problem already (mostly) solved. More importantly, I would miss all the tooling provided by Apollo or Relay.

Today, I'll use the Apollo client. I chose it mostly because of its great integration with Redux. Also, the Apollo team wrote excellent documentation, and published a lot of tutorials and articles about GraphQL. It makes the learning curve smoother for Apollo than for Relay. Most people choose Apollo over Relay these days, so it's the safest choice, too. Finally, Apollo plays well with any GraphQL server, while Relay requires a Relay server.

yarn add react-apollo

If you want more reasons to choose either Apollo or Relay, check out these two articles: Relay vs Apollo: An un-opinionated comparison of GraphQL clients for React apps, and Choosing a GraphQL Client: Apollo vs. Relay. Caveat: both use the old version of Relay (which will be abandoned), instead of Relay Modern.

Relay vs Apollo

Loading Data With GraphQL

The entry point of the Apollo client is a Higher Order Component (HOC) named graphql. Its signature is the following:

import { graphql } from 'react-apollo';

const MyComponent = ({ data }) => // ...

export default graphql(query, options)(MyComponent);

The graphql HOC injects a data prop into the decorated component, using the data fetched from the GraphQL backend.

The query argument must be a GraphQL document, which is an object-oriented representation of a GraphQL query. Since I manipulate a GraphQL string, I have to convert it to a document first. That's the purpose of the gql template literal, also provided by the react-apollo library. In practice, here is the query requesting all the data needed for the home page:

// in App.js
import { gql } from 'react-apollo';

const query = gql`
query homePageQuery {
    currentUser: User {
        id
        username
        full_name
        avatar_url
    }
    tweets: Tweets(limit:10, sort_field:"date", sort_order:"desc") {
        id
        body
        date
        author: Author {
            id
            username
            full_name
            avatar_url
        }
        stats: Stats {
            views
            likes
            retweets
            responses
        }
    }
}
`;

I named the query homePageQuery, and it returns two fields: currentUser, and tweets (I used GraphQL aliases here to rename User to currentUser and Tweets to tweets).

As explained in the second article in this series, always name your queries to make debugging easier.

Let's use the homePageQuery with the App component:

// in App.js
import React from 'react';
import { gql, graphql } from 'react-apollo';
import { LinearProgress } from 'material-ui/Progress';

import Header from './Header';
import Tweet from './Tweet';

export const App = ({ data: { loading, currentUser, tweets } }) =>(
    <div>
        <Header currentUser={currentUser} />
        {loading && <LinearProgress />}
        {!loading && tweets.map(tweet =>
            <Tweet key={tweet.id} tweet={tweet} />
        )}
    </div>
);

const query = gql`...`; // the homePageQuery defined above

export default graphql(query)(App);

When the component mounts, the graphql HOC injects a data prop with the value { loading: true }. At the same time, the HOC sends the query to the GraphQL backend. When the response arrives, the HOC passes the result in the data prop, so its value becomes { loading: false, currentUser: {...}, tweets: [...] }, and the component re-renders. The loading field makes it super easy to show and hide a loading indicator while the client waits for the server response. No need to mess around with the AJAX ready state!

The <App> component doesn't yet have the context of where the GraphQL server is. This is the job of the <ApolloProvider> component, which should be placed higher in the component hierarchy. This component expects an ApolloClient instance configured for the GraphQL backend. Let's modify the main index.js file:

// in index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, ApolloProvider, createNetworkInterface } from 'react-apollo';

import App from './App';

const client = new ApolloClient({
    networkInterface: createNetworkInterface({
        uri: 'http://localhost:4000/graphql',
    }),
});

ReactDOM.render(
    <ApolloProvider client={client}>
        <App />
    </ApolloProvider>,
    document.getElementById('root'),
);

The application is now connected to the GraphQL backend, and data from the server replace data from the default props:

The application

To summarize, all it takes to connect a React component to a GraphQL backend is to decorate it with the graphql HOC, and to pass it a GraphQL query in the parameters.

Problem solved? Not quite yet. No real life application is as simple as a list of tweets. Each new requirement adds more complexity, and this is where the tooling and good practices make the difference. This is also where Apollo starts to shine.

To avoid repeating the user fields twice in the GraphQL query, I can use a GraphQL fragment to keep the code DRY.

// in App.js
import { gql } from 'react-apollo';

const query = gql`
fragment UserFields on User {
    id
    username
    full_name
    avatar_url
}

query homePageQuery {
    currentUser: User {
        ...UserFields # Use the fragment here
    }

    tweets: Tweets(limit:10, sort_field:"date", sort_order:"desc") {
        id
        body
        date
        author: Author {
            ...UserFields # Use the fragment here, too
        }
        stats: Stats {
            views
            likes
            retweets
            responses
        }
    }
}
`;

Checking GraphQL Syntax While Coding

My IDE (VSCode) supports ESLint, so I can take advantage of syntax check and highlighting of GraphQL queries inside the gql tag. To enable it, I added the eslint-plugin-graphql to my ESLint configuration. This boosts my productivity, and gives me more confidence while coding. I definitely recommend using it.

Syntax highlighting

And since GraphQL is a strongly typed language, it's possible to get type validation and autosuggestion of GraphQL queries. In VSCode, this requires the installation of the GraphQL plugin. Another must have if you're going to write GraphQL queries regularily.

If you're using prettier to format your code (you really should), you'll be happy to know that it understands the gql tag too, and is able to reformat your GraphQL code.

The Apollo Client Developer Tools

The Apollo team has created a fantastic debugging tool called the Apollo Client Developer Tools, packaged as a browser extension for Chrome.

The developer tools include a GraphiQL interface automatically connected to the backend used by the ApolloClient. It allows to explore the GraphQL API while developing.

The application

The developer tools also include Queries and Mutations tabs, where I can see the calls made by my app in real time. The GraphQL code is highlighted for readability, and I can run them in the included GraphiQL interface with the click of a button.

The applicationThe application

Finally, the developer tools also include a store explorer, which lets me know exactly what's in the local Apollo cache (more on this in a few minutes).

The application

This extension is a must have for every frontend developer building a GraphQL-powered app. I simply can't live without it!

Binding Variables To GraphQL Queries

In a real life Twitter app, clicking on a tweet shows a new "tweet" page, where the user can read the reactions to the tweet. This means my app will have multiple pages, so I need to introduce a router here. Let's add react-router, the de facto standard in React router land.

yarn add react-router-dom

My app should have two pages: HomePage, and TweetPage. As the Header component appears in both pages, I'll put it in the main App component. Also, the list of tweets is no longer necessary in the App component, so I'll remove it from the GraphQL query of the App component.

// in App.js
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { gql, graphql } from 'react-apollo';

import Header from './Header';
import HomePage from './HomePage';
import TweetPage from './TweetPage';
import { userFragment } from './fragments';

export const App = ({ data: { currentUser } }) =>
    <Router>
        <div>
            <Header currentUser={currentUser} />
            <Route exact path="/" component={HomePage} />
            <Route exact path="/:id" component={TweetPage} />
        </div>
    </Router>;

const query = gql`
${userFragment}

query appQuery {
    currentUser: User {
        ...UserFields
    }
}
`;

export default graphql(query)(App);

I could have put the query for the current user inside the Header component, but as you'll see later, including it in the App component provides interesting benefits.

I export both the component decorated with graphql (it's the default export) and the non-decorated App component. That way, I can easily unit test the App component by injecting fake data props.

Note: I extracted the userFragment into a fragment.js file, so that I can reuse it across many queries.

// in fragments.js
import { gql } from 'react-apollo';

export const userFragment = gql`
fragment UserFields on User {
    id
    username
    full_name
    avatar_url
}
`;

Here is the new HomePage component, decorated with a GraphQL query for the list of tweets:

// In HomePage.js
import React from 'react';
import { gql, graphql } from 'react-apollo';
import { LinearProgress } from 'material-ui/Progress';

import Tweet from './Tweet';
import { userFragment } from './fragments';

export const HomePage = ({ data: { loading, tweets } }) =>(
    <div>
        {loading && <LinearProgress />}
        {!loading && tweets.map(tweet =>
            <Tweet key={tweet.id} tweet={tweet} />
        )}
    </div>
);

const query = gql`
${userFragment}

query homePageQuery {
    tweets: Tweets(limit:10, sort_field:"date", sort_order:"desc") {
        id
        body
        date
        Author {
            ...UserFields
        }
        Stats {
            views
            likes
            retweets
            responses
        }
    }
}
`;

export default graphql(query)(HomePage);

Let's update the Tweet component so that, when users click on a tweet, they navigate to the tweet page:

// in Tweet.js
import { Link } from 'react-router-dom';

const Tweet = ({ tweet }) => (
    <Link to={`/${tweet.id}`}>
        <Card>
            ...
        </Card>
    </Link>
);

export default Tweet;

Now, for the interesting part: passing variables to a GraphQL query. The new TweetPage component needs to grab the tweet id from the URL, and pass this id to the GraphQL query as a variable. React router makes URL parameters available to pages by injecting a match prop. How am I going to pass the id from react-router to graphql?

// in TweetPage.js
import React from 'react';
import { gql, graphql } from 'react-apollo';
import { LinearProgress } from 'material-ui/Progress';

import Tweet from './Tweet';
import { userFragment } from './fragments';

export const TweetPage = ({ data: { loading, tweet } }) => (
    <div>
        {loading && <LinearProgress />}
        {!loading && <Tweet tweet={tweet} />}
    </div>
);

const query = gql`
${userFragment}

query tweetPageQuery($id: ID!) {
    tweet: Tweet(id: $id) {
        id
        body
        date
        Author {
            ...UserFields
        }
        Stats {
            views
            likes
            retweets
            responses
        }
    }
}
`;

export default graphql(query, {
    options: ({ match }) => ({ variables: { id: match.params.id } })
})(TweetPage);

Note that the tweetPageQuery query requires an $id parameter which is of type ID.

I configured the graphql HOC so that it populates the variables options based on the match prop. This options property can be either a simple object, or a function receiving the component props. Note that I didn't prefix the id variable with a $ - the Apollo client does it automatically.

And that's how to populate GraphQL variables in Apollo.

Merging Queries

If I now browse the application, I can see that two requests are emitted: one for the currentUser, and another for the tweets:

The application

Apollo offers a way to merge all the queries issued in a (configurable) time interval into a single query, but with a major drawback: the resulting query is not a standard GraphQL query. That means that I need a specific package on the server side to handle Apollo merged queries (called "Query Batching" in the Apollo documentation).

Unfortunately, this package (apollo-server-express) is not compatible with the one I used in the previous post in this series (express-graphql). Nevermind. For the sake of learning, let's imagine that I'm using apollo-server-express on the server side, and see how to enable batched queries on the client side.

I just need to replace createNetworkInterface by createBatchingNetworkInterface in the ApolloClient initialization:

// in index.js
// ...
import {
    ApolloClient,
    ApolloProvider,
    createBatchingNetworkInterface,
} from 'react-apollo';
import App from './App';

const networkInterface = createBatchingNetworkInterface({
    uri: 'http://localhost:4000/graphql',
});
const client = new ApolloClient({ networkInterface });

ReactDOM.render(
    <ApolloProvider client={client}>
        <App />
    </ApolloProvider>,
    document.getElementById('root'),
);

Under the hood, instead of sending one query for each component, the BatchedNetworkInterface accumulates the queries issued by all the components, and sends them as an array of queries. This is very similar to what we did at marmelab with the koa-multifetch middleware.

Note: The Apollo client used to be able to merge queries into a real GraphQL query. But they've removed this feature in late 2016, because it was too difficult to implement in corner cases. Relay Modern managed to implement this approach correctly, but it requires both specific tooling on the server side, and a radical change in the client code.

Even if they are convenient, we don't use either one of Apollo's or Relay's batching features for our GraphQL apps at marmelab. The sad truth is that there is no consensus about how to batch queries in GraphQL yet, and the tools are not mutually compatible. I hope that, in the near future, both Apollo and Relay will provide a better way to manage batched queries. For now, it's a serious limitation of both client libraries.

Adding Client-Side Cache

Apollo keeps the results of previous queries in an internal cache called the store. It reuses data from the store in some cases, but not in all cases. Let's see that in practice.

When a user navigates from the list to a tweet, and then back to the list, the browser doesn't issue a new request to the GraphQL server. The store already contains the response for the list of tweets query, so Apollo reuses it. However, when a user comes to a tweet page from the list, the browser makes another request for the single tweet - even though the app already has the data for this tweet from the list. That's because Apollo does not assume that data from a query can be reused for another one.

In my case, this default caching strategy is a bit weak. When a user navigates to a single tweet page coming from the home, I know the store already has all the required data already. How can I force Apollo to use data from the store, even it if comes from a different query?

I need to declare a custom resolver on the client:

// in index.js
// ...
import {
    ApolloClient,
    ApolloProvider,
    createNetworkInterface,
    toIdValue,
} from 'react-apollo';

/**
 * Generates a unique id for an entity
 *
 * @example 'Tweet__12'
 */
const dataIdFromObject = object => `${object.__typename}__${object.id || object.tweet_id}`;

const customResolvers = {
    Query: {
        Tweet: (_, { id }) => toIdValue(dataIdFromObject({ __typename: 'Tweet', id })),
    },
};
const client = new ApolloClient({
    networkInterface,
    dataIdFromObject,
    customResolvers,
});

Here, I declared a custom dataIdFromObject function. Apollo calls this function whenever it needs to uniquely identify an entity (in my case, Tweet, User and Stats). I used the entity type name from GraphQL, concatenated with the object id. That's actually the default identifier - but I need to use this function in the resolver, so I have to redefine it.

In the custom resolver for the Tweet query, I call the toIdValue function provided by Apollo, using the unique id of the object to look for. This way, Apollo knows which object I'm expecting, and can return it directly from the store if available. Otherwise, it will send the query to the server.

Note: I made sure to use tweet_id for the Stats, as we only have one Stats object for each Tweet. I also have to ensure that this tweet_id is requested by all the queries which return a Tweet and its Stats. I should add a fragment for the Tweet objects to avoid such cumbersome changes in multiple files. Let's do that now:

// in fragments.js

export const tweetFragment = gql`
fragment TweetFields on Tweet {
    id
    body
    date
    Author {
        ...UserFields
    }
    Stats {
        tweet_id
        views
        likes
        retweets
        responses
    }
}
`;

I need to update the HomePage component to use this new fragment:

// in HomePage.js
import { tweetFragment, userFragment } from './fragments';

const query = gql`
query homePageQuery {
    tweets: Tweets(limit:10, sort_field:"date", sort_order:"desc") {
        ...TweetFields
    }
}

${userFragment}
${tweetFragment}
`;

And the TweetPage component, too:

// In TweetPage.js
import { tweetFragment, userFragment } from './fragments';

export const tweetPageQuery = gql`
query tweetPageQuery($id: ID!) {
    tweet: Tweet(id: $id) {
        ...TweetFields
    }
}

${userFragment}
${tweetFragment}
`;

Now, if I navigate back and forth between any tweets and the home page, I can see that only the initial two requests are made:

The application

The Apollo caching policy is controlled by an option of the graphql HOC called fetchPolicy. I can choose to never use cached data, or to display cached data immediately but still request an up to date version, or to show only cached data. All options are described in the Apollo client documentation.

Apollo makes client-side caching relatively easy, and this is good news for client-side performance: an app that doesn't need to fetch a backend is a fast app! Most of all, it's flexible enough to adapt to most use cases.

Calling Mutations

To illustrate how to update data using GraphQL mutations, let's add a page component for creating a new tweet. It's a simple page with a controlled textarea. Notice the submit prop, a function taking the textarea content as argument:

// in NewTweetPage.js
import React, { Component } from 'react';
import Button from 'material-ui/Button';
import { gql, graphql } from 'react-apollo';

import { tweetFragment } from './fragments';

export class NewTweetPage extends Component {
    state = { body: '' };

    handleChange = event => {
        this.setState({ body: event.target.value });
    }

    handleSubmit = () => {
        this.props.submit(this.state.body);
    }

    render() {
        const { body } = this.state;
        return (
            <div>
                <label htmlFor="body">Enter your message</label>
                <textarea value={body} onChange={this.handleChange} rows={4} />
                <Button onClick={this.handleSubmit} color="primary" raised>
                    Send
                </Button>
            </div>
        );
    }
}

I need to bind this component to the following GraphQL mutation:

const mutation = gql`
mutation createTweet($body: String!) {
    createTweet(body: $body) {
        ...TweetFields
    }
}

${tweetFragment}
`;

Just like for for the tweet query, I specified a $body parameter, which will be passed to the inner mutation call. Once again, the place to bind the component methods with the GraphQL variables is the graphql HOC options:

export default graphql(mutation, {
    props: ({ mutate }) => ({
        submit: body => mutate({ variables: { body }}),
    }),
})(NewTweetPage);

Apollo gives access to the mutation via a callback prop called mutate. I use the props option to inject props into the decorated component - in this case, the submit prop. It allows me to hide the mutation call logic from the NewTweetPage component.

As you can see, binding data to a mutation takes only one line of code. It may seem a bit convoluted the first time you see it, but after doing it once or twice, using the graphql props option feels super convenient.

Even if a mutation usually returns data, the Apollo documentation recommends that you don't rely on the result. Instead, you should reload the data using a GraphQL query. The Apollo cache should avoid a server round trip in most cases. Besides, in many cases you can use optimistic UI to display the result without waiting for the server response (as I'll explain in a few minutes).

Reacting To Mutations

Once the mutation request has finished, I need to redirect the user to the list of tweets. To determine when I can do the redirection, I rely on the fact that the mutate function returns a promise - and so does my custom submit prop.

I'll use react-router's withRouter HOC to inject the history object into my component, and do the redirection using history.push(). I chose to put this redirection in a prop injected by the graphql HOC rather than in the component code itself, in order to make it testable:

// ...
import { compose, gql, graphql } from 'react-apollo';
import { withRouter } from 'react-router';

export class NewTweetPage extends Component {
    // ...
    handleSubmit = () => {
        const { submit, redirectToHome } = this.props;
        submit(this.state.body)
            .then(() => redirectToHome());
    }
    // ...
}

const mutation = gql`...`;

export default compose(
    withRouter,
    graphql(mutation, {
        name: 'createTweet',
        props: ({ createTweet, ownProps: { history } }) => ({
            submit: body => createTweet({ variables: { body }}),
            redirectToHome: () => history.push('/'),
        }),
    }),
)(NewTweetPage);

When calling props, the graphql passes my component props in an ownProps parameter to avoid conflicts with the Apollo props. That's where I grab the history function, which is injected by the withRouter HOC.

The compose function allows to compose multiple HOCs on a component. In my opinion this is more readable than the equivalent code without compose, which accumulates parentheses in a LISP way:

export default withRouter(graphql(mutation, {
    name: 'createTweet',
    props: ({ createTweet, ownProps: { history } }) => ({
        submit: body => createTweet({ variables: { body }}),
        redirectToHome: () => history.push('/'),
    }),
})(NewTweetPage));

I used the name option to rename the mutate function to createTweet. I systematically do this to have a method name consistent with the mutation name. This also proves useful when I have to bind multiple mutations to a single component: I avoid name conflicts on the mutate prop. In case you wonder how to call multiple mutations, you just need to compose the component with the graphql HOC more than once.

Updating The UI In An Optimistic Way

What if the network is slow? What if the server is currently very busy? The current NewTweetPage would be frozen until the server returns, which is bad UX. I could insert a loading animation to help the end user wait for the result, but I can do better. And what's better than a loading animation? No loading animation!

Indeed, I'll use the data I included in the mutation to display the new tweet even before the server acknowledges the change. This is called optimistic rendering. This was one of the killer features of Meteor.js, and fortunately the Meteor team ported it to the Apollo client.

To achieve optimistic rendering with Apollo, I add two options to the mutate call (renamed createTweet): optimisticResponse, and update.

// in NewTweetPage.js
import { query as homePageQuery } from './HomePage';
// ...
class NewTweetPage extends Component {
    // ...
    handleSubmit = () => {
        const { submit, redirectToHome } = this.props;
        // The submit promise resolves once the server has responded
        submit(this.state.body);
        // As the optimistic response is set up, don't wait for the promise
        // to be resolved, but redirect the user to the list immediately
        redirectToHome();
    }
    // ...
}

export default compose(
    withRouter,
    graphql(mutation, {
        name: 'createTweet',
        props: ({ createTweet, ownProps: { currentUser, history } }) => ({
            submit: body => createTweet({
                variables: { body },
                optimisticResponse: {
                    __typename: 'Mutation',
                    createTweet: {
                        __typename: 'Tweet',
                        id: 'newTweet',
                        date: new Date(),
                        body,
                        Author: {
                            __typename: 'User',
                            id: currentUser.id,
                            avatar_url: currentUser.avatar_url,
                            username: currentUser.username,
                            full_name: currentUser.full_name,
                        },
                        Stats: {
                            __typename: 'Stats',
                            tweet_id: 'newTweet',
                            views: 0,
                            likes: 0,
                            retweets: 0,
                            responses: 0,
                        },
                    },
                },
                update: (store, { data: { createTweet } }) => {
                    // Read the data from the cache for this query
                    const localData = store.readQuery({ query: homePageQuery });
                    // Add the new tweet from the mutation to the beginning
                    localData.tweets.unshift(createTweet);
                    // Write the data back to the cache
                    store.writeQuery({ query: homePageQuery, localData });
                },
            }),
            redirectToHome: () => history.push('/'),
        }),
    }),
)(NewTweetPage);

Don't be frightened by the long graphql function call, it's actually quite straightforward.

The optimisticResponse option allows me to define a fake response from the server, that Apollo will use to update the local store before the server returns. The fake response must match the createTweet mutation response format, and contain GraphQL type information, hence the __typename I have to add for each entity. Note that I use a currentUser prop to fill in the new tweet Author fields; I'll explain how the parent App component passes that prop to NewTweetPage in a few moments. I also need to provide an empty Stats, as it's required by the schema.

As for the update option, it's a function taking two parameters. The store is the Apollo internal data store, which keeps the responses of all previous queries. It's a registry object where the keys are the queries. The second parameter, data, contains the result of the query. Apollo calls this update function twice: first with the data returned by the optimisticResponse, and then with the data from the GraphQL server when the mutation resolves.

Here is how I inject the optimistic response to the store: I first get the data for the homePageQuery query (exported by the HomePage.js file). I then put the newly created tweet at the first position of the tweets array. Note that it is named createTweet according to the mutation name. Finally, I write that updated data back into the store.

The optimisticResponse and update props may look hard to write at first sight. But imagine the boilerplate code that you would have to write to reproduce the same optimistic rendering : it would be a lot more complicated. Optimistic rendering is not an easy task, but Apollo makes it manageable.

Note: As Apollo executes the update function twice, you may fear that a new tweet gets added twice to the list. Fortunately, when receiving the real result from the GraphQL server, the Apollo client rolls back the update done with the optimistic response first, then calls the update() function again. For the end user, if the optimistic response and the server response are the same, everything happens as if the page was only rendered once.

For more information about the store object, I invite you to read the direct cache access documentation on the Apollo website, as well as this article by Caleb Meredith: Apollo Client’s new imperative store API.

Plugging In All Together

To make sure everything works as expected, I still have to add the NewTweetPage as a new route:

// In App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import HomePage from './HomePage';
import Header from './Header';
import TweetPage from './TweetPage';
import NewTweetPage from './NewTweetPage';

const App = ({ data: { currentUser } }) =>
    <Router>
        <div>
            <Header currentUser={currentUser} />
            <Switch>
                <Route exact path="/" component={HomePage} />
                <Route
                    exact
                    path="/new"
                    render={() => <NewTweetPage currentUser={currentUser} />}
                />
                <Route exact path="/:id" component={TweetPage} />
            </Switch>
        </div>
    </Router>

// ...
export default graphql(query)(App);

I have to use react-router's Switch component here to prevent the last route from matching the /new path. Moreover, the route matching the /new path must be declared before the route matching a tweet id.

Note: I used the render prop for the /new route so that I can pass additional props to the NewTweetPage component. Here, it's the currentUser prop (I told you it would be convenient to have that user inside the App component and not in the Header).

I also updated the New Tweet button in the Header component to link to the /new path:

// in Header.js
// ...
import { Link } from 'react-router-dom';

const Header = ({ data: { currentUser } }) => (
    <AppBar position="static" color="default">
        <Toolbar>
            {currentUser &&
                <Avatar alt={currentUser.full_name} src={currentUser.avatar_url} />
            }
            <Typography component="span" type="title" color="inherit">
                Home
            </Typography>
            <IconButton component={Link} to="/new">
                <ModeEditIcon />
            </IconButton>
        </Toolbar>
    </AppBar>
);

Now if I create a new tweet, I can see that it appears immediately on the home page. When the mutation resolves, the new tweet is updated using the server response. Since it's the same tweet as in the optimistic response, the UI doesn't change. With network throttling enabled in the Chrome devtools, I can see that the new tweet appears before the GraphQL request is resolved.

The application

Handling Server Errors

My Twitter clone is progressively looking like a real life application. But what if the server rejects the mutation? What if the network connection is lost? Let's simulate that with the Chrome devtools.

The application

Did you notice the error in the console? The mutate function (renamed createTweet) returns a rejected promise. What's more interesting is that the Apollo client automatically rolls back the optimistic changes! As you can see in the screencast above, the end user has hardly enough time to see it appearing.

The only thing left to do is to notify the user about the mutation failure. Since the mutate function returns a promise, I can catch errors there:

// in NewTweetPage.js
// ...

export default compose(
    withRouter,
    graphql(mutation, {
        name: 'createTweet',
        props: ({ createTweet, ownProps: { currentUser, history } }) => ({
            submit: body => createTweet({
                // ...
            }).catch(error => {
                console.error(error);
                alert(`
                    Sorry, we weren't able to send your tweet.
                    Please check your network connection and retry.`
                );
            }),
            redirectToHome: () => history.push('/'),
        }),
    }),
)(NewTweetPage);

Using an alert is really ugly, right? It would be better to display a nice notification using an in-app notification (called a Snackbar in Material Design lingo). But, when the mutation is rejected, the user has already been redirected to the home page, because of the optimistic rendering. So the user is no longer in the NewTweetPage. I need a way to pass the error from one component to the other, asynchronously.

Using a Custom Redux Store To Show Notifications

I could store an error value in the state of the App component, and pass it as props to the pages. But I can do better than that.

Let's call Redux to the rescue! Redux is a state management library for React - probably the best one. It's also the library used by Apollo to manage its internal store. And it's pretty easy to extend the Apollo store, by adding (or composing) new reducer functions. It's a bit long to describe here, because I must initialize a Redux store. But if you've already used Redux before, you'll be in a familiar place. Otherwise, I recommend that you take a few minutes to read the Redux Gist before going on.

I'll start by creating the notifications duck. In case you don't know, a duck is a redux pattern where you group the related action types, action creators, and reducers into the same file, organizing files by feature. This makes it easier to reason about a given feature. The reducer should be the default export in the file. You can read more about the duck pattern in this article by S.C. Barrus: The Ducks File Structure for Redux.

// in notifications.js
import uuid from 'uuid';

// action type constants
export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';

// action creators
export const addNotification = message => ({
    type: ADD_NOTIFICATION,
    payload: message,
});
export const removeNotification = id => ({
    type: REMOVE_NOTIFICATION,
    payload: id,
});

const initialState = [];

// the reducer function
export default (previousState = initialState, action) => {
    if (action.type === ADD_NOTIFICATION) {
        return [
            ...previousState,
            { id: uuid.v1(), message: action.payload },
        ];
    }

    if (action.type === REMOVE_NOTIFICATION) {
        const removedIndex = previousState.findIndex(n => n.id === action.payload);
        return [
            ...previousState.slice(0, removedIndex),
            ...previousState.slice(removedIndex + 1),
        ]
    }

    return previousState;
}

Then, I bootstrap a Redux store, composed of the Apollo client reducer and my custom notifications reducer. I pass this store to the ApolloProvider component, to be used instead of the default Apollo store:

// in index.js
// ...
import {
    createStore,
    combineReducers,
    applyMiddleware,
    compose,
} from 'redux';
import notificationsReducer from './notifications';
// ...

const client = new ApolloClient({
    networkInterface,
    dataIdFromObject,
    customResolvers,
});

const store = createStore(
    combineReducers({
        notifications: notificationsReducer,
        apollo: client.reducer(),
    }),
    {}, // initial state
    compose(
        applyMiddleware(client.middleware()),
        // If you are using the devToolsExtension, you can add it here also
        typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined'
            ? window.__REDUX_DEVTOOLS_EXTENSION__() :
            f => f,
    )
);

ReactDOM.render(
    <ApolloProvider store={store} client={client}>
        <App />
    </ApolloProvider>,
    document.getElementById('root'),
);

The Apollo client instance exposes reducer and middleware methods. They facilitate the creation of a custom Redux store.

Note: No need to include your own react-redux <Provider> component - it's included by the ApolloProvider component, which uses Redux internally.

Now, let's update the HomePage component to show a notification when the store contains one:

// in HomePage.js
// ...
import { connect } from 'react-redux';
import Notification from './Notification';
import { removeNotification } from './notifications';
// ...

class HomePage extends Component {
    // ...
    handleNotificationClose = notificationId => {
        this.props.removeNotification(notificationId);
    }

    render() {
        const { loading, notifications, tweets } = this.props;
        return (
            <div>
                {loading && <LinearProgress />}
                {!loading && tweets.map(tweet =>
                    <Tweet key={tweet.id} tweet={tweet} />
                )}
                {notifications.map(notification =>
                    <Notification
                        key={notification.id}
                        onClose={this.handleNotificationClose}
                        notification={notification}
                    />
                )}
            </div>
        );
    }
}

// ...
const mapStateToProps = state => ({ notifications: state.notifications });

export default compose(
    connect(mapStateToProps, { removeNotification }),
    graphql(homePageQuery, {
        // ...
    }),
)(HomePage);

The Notification component simply uses material-ui's Snackbar component:

import React, { Component } from 'react';
import Snackbar from 'material-ui/Snackbar';

const anchorOrigin = { vertical: 'bottom', horizontal: 'center' };

class Notification extends Component {
    handleClose = () => {
        this.props.onClose(this.props.notification.id);
    }

    render() {
        const { notification: { message } } = this.props;
        return (
            <Snackbar
                anchorOrigin={anchorOrigin}
                autoHideDuration={6e3}
                message={<span>{message}</span>}
                onClick={this.handleClose}
                onRequestClose={this.handleClose}
                open
            />
        );
    }
}

export default Notification;

Finally, I updated the NewTweetPage to dispatch a notification on error, instead of showing an alert:

// in NewTweetPage.js
...
import { connect } from 'react-redux';
import { addNotification } from './notifications';
...

export default compose(
    withRouter,
    connect(null, { addNotification }),
    graphql(mutation, {
        name: 'createTweet',
        props: ({ createTweet, ownProps: { currentUser, history, addNotification } }) => ({
            redirectToHome: () => history.push('/'),
            submit: body => {
                createTweet({
                    // ...
                }).catch(error => {
                    console.error(error);
                    addNotification(`
                        Sorry, we weren't able to send your tweet.
                        Please check your network connection and retry.`
                    );
                });
            }
        }),
    }),
)(NewTweetPage);

And here is the result:

The application

You'll probably need to add a custom Redux reducer to the Apollo store for notifications, too. Fortunately, you only need to do it once, and then you can dispatch notifications from anywhere in the app.

Including Authentication Credentials

Now that users can post data, my Twitter app needs an authentication layer. I won't expand here on how the user authenticates (a classical login form sending a connection request, and receiving a token in return). But I'll show how to include the token in all requests to the GraphQL server. Oh, and since a secure API requires both a token header and an HTTP-only cookie, I'll also include these credentials in the call.

All it takes to include static headers in every request to the GraphQL server is a modification in the network interface configuration. For dynamic headers, Apollo supports middleware functions - just like you would add middlewares in an Express app:

// in index.js
const networkInterface = createNetworkInterface({
    uri: '/graphql',
    opts: { // additional options for fetch
        credentials: 'same-origin', // your backend server is on the same domain
        credentials: 'include', // your backend server is not on the same domain
    },
});

const includeAuthTokenMiddleware = {
    applyMiddleware(req, next) {
        if (!req.options.headers) {
            req.options.headers = {};  // Create the header object if needed.
        }
        // get the authentication token from local storage if it exists
        const token = localStorage.getItem('token');
        req.options.headers.authorization = token ? `Bearer ${token}` : null;
        next();
    }
};

networkInterface.use([includeAuthTokenMiddleware]);

const client = new ApolloClient({
    networkInterface,
});

You can read more about how to customize the networkInterface for authentication in the Apollo documentation.

Loading Paginated Data

The home page currently displays only 2 tweets. What happens when the database has hundreds of them for a given user? I'll have to limit the number of tweets requested by the component, and include a way to load more tweets on demand.

The Tweets query exposed by the server accepts two parameters for pagination: limit, and skip. You may remember that I hard coded limit to 10 in the homePageQuery before. I'll add a skip variable to the query.

To manage the skip parameter, let's add a Load more button in the HomePage component:

// in HomePage.js
// ...
import Button from 'material-ui/Button';

class HomePage extends Component {
    state = { skip: 5 };

    handleClick = () => {
        this.props.loadMore(this.state.skip)
            .then(() => {
                this.setState(({ skip }) => ({ skip: skip + 5 }));
            });
    }

    render() {
        return (
            <div>
                {/* ... */}
                <Button onClick={this.handleClick}>
                    Load more
                </Button>
            </div>
        );
    }
}

I'll define the loadMore props through the graphql props option, but first, let's update the homePageQuery so that it takes a skip variable:

// in HomePage.js
// ...
export const homePageQuery = gql`
query homePageQuery($skip: Int!) {
    tweets: Tweets(skip:$skip, limit:10, sort_field:"date", sort_order:"desc") {
        ...TweetFields
    }
}

${userFragment}
${tweetFragment}
`;

Now, it's time to update the graphql HOC call:

// in HomePage.js
...
export default compose(
    connect(mapStateToProps, { removeNotification }),
    graphql(homePageQuery, {
        options: {
            variables: { skip: 0 },
        },
        props: ({ data: { loading, tweets, fetchMore } }) => ({
            loading,
            tweets,
            loadMore: skip =>
                fetchMore({
                    variables: { skip },
                    updateQuery: (previousResult, { fetchMoreResult }) => ({
                        ...previousResult,
                        tweets: [
                            ...previousResult.tweets,
                            ...fetchMoreResult.tweets,
                        ],
                    }),
                })
        }),
    })
)(HomePage);

In addition to loading, Apollo injects another utility function in the data prop, called fetchMore. It allows to do a new GraphQL query, and merge the result into the original result. I use it in the loadMore custom prop. When called, it will execute fetchMore, overriding the initial variables with the specified skip argument.

The updateQuery option is called to merge the results from fetchMore with the current store. Here, I just add the new tweets to the existing array from the previous queries.

I love that Apollo takes care of most common use cases, like pagination. The loadMore option offers a great shortcut for that. You can read more about pagination with the Apollo client in the Apollo documentation.

Refactoring Query Variables

Wait! If I try to create a new Tweet, now I get a bunch of error messages in the console, and the new tweet does not appear anymore after the redirection.

The application

That's because the homePageQuery now requires the skip variable, and Apollo needs this variable in the update method of the NewTweetPage component as well, so that it can update its store correctly.

This will bite you, too : updating the parameters of a GraphQL query that is reused in several places will break your app. That's one of the reasons why you should have automated tests to detect that kind of regression.

Let's fix that bug. First, I export the default variables for the homePageQuery:

// in HomePage.js
// ...
export const homePageQueryVariables = { skip: 0 };

export default compose(
    connect(mapStateToProps, { removeNotification }),
    graphql(homePageQuery, {
        options: {
            variables: homePageQueryVariables,
        },
        props: ({ data: { loading, tweets, fetchMore } }) => ({
            ...
        }),
    }),
)(HomePage);

Now, I must update the graphql HOC update method inside the NewTweetPage component:

// In NewTweetPage.js
// ...
import { homePageQuery, homePageQueryVariables } from './HomePage';
// ...

export default compose(
    withRouter,
    connect(null, { notify: notifyAction }),
    graphql(mutation, {
        props: ({ mutate, ownProps: { currentUser, history, notify } }) => ({
            redirectToList: () => history.push('/'),
            submit: body => {
                mutate({
                    // ...
                    update: (store, { data: { createTweet } }) => {
                        // pass the variables here
                        const localData = store.readQuery({
                            query: homePageQuery,
                            variables: homePageQueryVariables,
                        });
                        localData.tweets.unshift(createTweet);
                        // pass the variables here, too
                        store.writeQuery({
                            query: homePageQuery,
                            variables: homePageQueryVariables,
                            localData,
                        });
                    }
                }).catch(error => {
                    console.error(error);
                    addNotification(`
                        Sorry, we weren't able to send your tweet.
                        Please check your network connection and retry.`
                    );
                });
            }
        }),
    }),
)(NewTweetPage);

Note that I passed the variables to both store.readQuery and store.writeQuery.

The application is now back to normal!

I'm lucky with my use case here, as I only have to insert the newly created tweet at the first position in the tweets array. If tweets were sorted in chronological order, things would be more complicated. I would have to push the new tweet at the end of the tweets array. But as I use the initial variables of the homePageQuery, the new tweet would be pushed at the end of the first page. I currently have no way to know whether the user requested to see all tweets on the HomePage component. Resolving this would imply some additional Redux state, where I would store the current state of displayed tweets (variables, end of available tweets from server, etc.). I would connect this state to the NewTweetPage, and use it to compute the variables.

Separating Views and Data Dependencies

In this article, you may have noticed that I collocated my components and their data dependencies (queries, mutations and the graphql HOC). I find that it makes it easy to reason about the application. On the other side, doing a refactoring like the one in the previous section is a little cumbersome. Not to mention the fact that, as an application grows, two components may need the same data dependency.

That means that for large code bases, it's often better to separate React components from the graphql logic. A good solution for that is to create higher order components for each data requirement.

For example, the logic for requesting the list of tweets could be moved to a withTweets HOC, as follows:

// in data/withTweets.js
import { gql, graphql } from 'react-apollo';

const listTweetsQuery = gql`...`;

const listTweetsQueryVariables = { /* ... */ };

export default graphql(listTweetsQuery, {
    options: {
        variables: listTweetsQueryVariables,
    },
    props: ({ data: { loading, tweets, fetchMore } }) => ({
        loading,
        tweets,
        loadMore: skip =>
            fetchMore({
                variables: { limit: 5, skip },
                updateQuery: (previousResult, { fetchMoreResult }) => ({
                    ...previousResult,
                    tweets: [
                        ...previousResult.tweets,
                        ...fetchMoreResult.tweets,
                    ],
                }),
            })
    }),
})

Using such an HOC in a page component is extremely straightforward:

// in HomePage.js
import withTweets from './data/withTweets';
// ...

export default withTweets(HomePage);

Unit Testing The GraphQL Logic

Now that the GraphQL logic is separated from the components, it's easier to test in isolation. But what can I test? Many of the graphql HOC options are pure functions, so they can be extracted, and unit tested.

For instance, I can refactor the withTweets HOC to export the updateQuery and loadMore functions:

// in data/withTweets.js
import { gql, graphql } from 'react-apollo';

const listTweetsQuery = gql`...`;
const listTweetsQueryVariables = { /* ... */ };

export const updateQuery = (previousResult, { fetchMoreResult }) => ({
    ...previousResult,
    tweets: [
        ...previousResult.tweets,
        ...fetchMoreResult.tweets,
    ],
});

export const loadMore = fetchMore => skip => fetchMore({
    variables: { limit: 5, skip },
    updateQuery,
});

export default graphql(listTweetsQuery, {
    options: {
        variables: listTweetsQueryVariables,
    },
    props: ({ data: { loading, tweets, fetchMore } }) => ({
        loading,
        tweets,
        loadMore: loadMore(fetchMore),
    }),
})

Even before writing the unit tests, I find that the code is now easier to read. That's, in my opinion, one of the greatest benefits of unit testing: it forces a better code architecture. And now, I can test the intermediate functions. Just like for the server side, I'll use Jest to run tests.

// in data/withTweets.spec.js
import { updateQuery }  from './withTweets';

describe('updateQuery', () => {
    it('concatenates tweets from fetchMore results to the previous result', () => {
        const previouResult = {
            foos: [{ bar: 1 }],
            tweets: [{ id: 1 }, { id: 2 }],
        };
        const fetchMoreResult = {
            tweets: [{ id: 5 }, { id: 6 }],
        }
        expect(updateQuery(previousResult, { fetchMoreResult })).toEqual({
            foos: [{ bar: 1 }],
            tweets: [{ id: 1 }, { id: 2 }, { id: 5}, { id: 6 }],
        });
    });
});

How about testing the React components themselves? It's possible if, like me, you always take care of exporting the non-decorated component, too:

export const HomePage = ({ data }) => // ...

export default graphql(homePageQuery, { /* ... */})(HomePage);

This enables me to unit test the non-decorated components, by simulating Apollo's data prop. I usually use enzyme for that, and there is nothing specific to GraphQL so I won't expand here.

As for unit testing the HOC itself (the default export), I wouldn't recommend it. HOC tests require quite a lot of plumbing (creating a mock context, providing a dummy component, decorating the component with the mock context, using mount() instead of shallow() in enzyme, etc.), and it is equivalent to testing the react-apollo package.

Note: The Apollo team bootstrapped a package called apollo-test-utils a few months ago, dedicated to mocking networkInterface. There is also a <MockedProvider> component in the react-apollo package. Both are quite rudimentary, I wouldn't recommend them for now. In my opinion, the Apollo tooling is lacking to make unit tests on the client easy.

If you want to test if a GraphQL-powered component works as expected, you'll have to turn to end-to-end testing. That's a lot more work than unit tests, but fortunately we've already covered that topic in another article: End to End (e2e) Testing React Apps With Selenium WebDriver And Node.js is Easier Than You Think. You'll also need a mock GraphQL server. Fortunately, Apollo provides a one-liner to create a mock server based on a GraphQL schema. You can read more about it in this article by Jonas Helfer: Mocking your server with just one line of code.

Storing GraphQL Queries In Separate Files

Separating the logic from the presentation is a good practice, but I can go further in the separation of concerns.

In this article, I wrote every queries and mutations as strings, and passed them to the gql template literal tag. However, strings are inconvenient to manipulate when you start to add extra fields, merge multiple queries, or include fragments. Besides, it means that the GraphQL code is parsed at runtime ; even if it happens only once (the parsing result is cached), it has a cost.

An alternative way to store GraphQL queries is to move them to static *.graphql files. This enables Persisted Queries, which offer performance and security benefits. Persisted queries will be introduced in the next post in this series. It also triggers compile-time syntax check of the GraphQL queries, which will raise errors before I deploy the code to production.

// in data/HomeQuery.graphql
#import "./TweetFragment.graphql"

query homePageQuery($limit: Int!, $skip: Int!) {
    tweets: Tweets(limit:$limit, skip:$skip, sort_field:"date", sort_order:"desc") {
        ...TweetFields
    }
}

Writing queries inside .graphql doesn't prevent me from reusing fragments across many files, thanks to the #import statement.

To process *.graphql files, I need to preprocess them using webpack or babel. The graphql-tag package comes with a webpack loader doing just that. There is also a babel plugin: babel-plugin-graphql-tag.

Once Babel is configured, I just need to import a *.graphql file to use the query in JavaScript:

// in data/withTweets.js
import { graphql } from 'react-apollo';
import listTweetsQuery from './HomeQuery.graphql';

export default graphql(listTweetsQuery, { /* ... */})

Using *.graphql files may reduce the production bundle size a bit, by removing the need for gql at runtime.

Just like separate data logic, static queries may not make sense for a small application. I recommend using it when the amount of GraphQL code in the components goes beyond a few lines. You can also read the 5 benefits of static GraphQL queries article by Sashko Stubailo to decide if it makes sense for your project.

Using Directives For Dynamic Queries

Using static query files has one major drawback: you can't alter the query at runtime anymore. A/B testing, or feature flags, are common use cases for altering a query at runtime. For instance, I might want to hide the Stats in my Twitter application for a subset of users, to test a new user experience.

Fortunately, you can add conditions to static GraphQL queries, too, using directives. We briefly introduced directives in the second post of this series. I can use the @include(if) directive to include the Stats only if the corresponding feature is activated.

// in data/TweetFragment.graphql
#import "./UserFragment.graphql"

fragment TweetFields on Tweet {
    id
    body
    date
    Author {
        ...UserFields
    }
    Stats @include(if: $withStats) {
        tweet_id
        views
        likes
        retweets
        responses
    }
}

You can learn more about the @include directive in the official GraphQL documentation.

I've introduced a new $withStats variable ; the home page query must therefore declare this variable:

// in data/HomeQuery.graphql
#import "./TweetFragment.graphql"

query homePageQuery($limit: Int!, $skip: Int!, $withStats: Boolean!) {
    tweets: Tweets(limit:$limit, skip:$skip, sort_field:"date", sort_order:"desc") {
        ...TweetFields
    }
}

Now, it's time to bind the $withStats variable to something significant. I'll introduce an environment variable called REACT_APP_FEATURE_STATS, using the mechanism provided by create-react-app.

// in data/withTweets.js
import { graphql } from 'react-apollo';
import listTweetsQuery from './HomeQuery.graphql';

export const listTweetsQueryVariables = {
    limit: 5,
    skip: 0,
    withStats: process.env.REACT_APP_FEATURE_STATS,
};

export default graphql(listTweetsQuery, {
    options: { variables: listTweetsQueryVariables },
    props: { /* ... */ },
});

Whenever I manipulate a GraphQL query on the client side (concatenating substrings, adding conditions, interpolating variables), a bell rings in my mind: You're doing it wrong! Static queries support fragments, conditions, and variables, so there is usually no reason to alter them at runtime.

Miscellaneous

My app is now complete. You can find the final code on GitHub: marmelab/GraphQL-example. I'll briefly cover a few more use cases, but feel free to skip to the conclusion if you're in a hurry.

Offline Usage

On flaky mobile networks, the ability to use an application while offline is becoming a must have feature. Out of the box, Apollo does not provide anything for that. However, Apollo is built on Redux, and the Redux ecosystem has already addressed the offline scenario with the redux-persist and redux-offline packages. So enabling offline mode on a GraphQL-powered web app is definitely possible.

This topic deserves multiple articles by itself, and Pete Corey already did a fantastic job at it. I invite you to read his two articles:

Note: The Apollo team has planned to address offline support in their roadmap.

Server Side Rendering

Server Side Rendering (SSR) a React app is not harder with GraphQL. You don't have to modify anything related to the GraphQL client code. The server responsible for the SSR of the client application simply makes HTTP requests to the GraphQL backend (which is usually the same server). However, Apollo is using Redux, so it implies the usual Redux store rehydratation work.

You can read more about this (with an actual SSR example) in the Apollo documentation.

Real Time Updates And Subscriptions

In addition to queries and mutations, GraphQL also supports subscriptions. They allow to address real time updates using the Publish/Subscribe pattern.

Exploring this subject requires another article! You can refer to those articles for a primer:

Be warned that subscription support is very young in both Apollo and Relay. I'd advise to wait for a few months until the tools settle down.

A Word About Relay Modern

Facebook's Relay Modern is an alternative to the Apollo client library. We haven't had any significant experience with it so far, but I advise you to read this article by Sashko Stubailo from the Apollo team to learn more about it: Exploring Relay Modern.

The main premise of Relay is that your data fetching should be just as modular as your UI.

In a nutshell, Relay is built from the ground up to allow developers to group React components with small GraphQL fragments. This ensures that each component is only responsible for its data, regardless of the other components requirements.

This is achieved by using several HOCs such as the createFragmentContainer, which does not send requests by itself. It just collects data requirements from the components. Once all data requirements are collected, a single query can be made to optimize network traffic.

However, be warned that adding Relay Modern requires an additional babel plugin in the client side, to allow a compilation step for your schema. This compilation step will help Relay to analyze your graphql code, do a compile-time check, and further optimize data fetching.

Conclusion

If you've read that far, thank you! I've tried to share my experience, and the one of my coworkers. I believe this post is enough to get you up and running to build your own GraphQL-powered app - in just one hour!

As you may have understood by reading this post, I really enjoy the Apollo client library, and the tooling around it. Developing React apps on top of a GraphQL server is definitely a pleasant experience - even more pleasant than developing REST-powered apps. And it makes complex features (optimistic rendering, caching) entirely manageable.

I have two regrets though, it's the lack of tools for unit tests, and the (absence of) solution for merged queries. One strong GraphQL promise is to reduce the number of HTTP calls, but on the client side, if you want to merge queries for more than one component, you still have to do it by hand. I have no doubt that this will be solved in the near future.

Now that you know how to use GraphQL both in the server side and in the client side, you probably made up your mind about the technology. Is it worth the hassle? We have a strong opinion on that. Read it in the next and last post in this series: Dive Into GraphQL Part V: Should You Use GraphQL?.

Did you like this article? Share it!