Chapter 6. GraphQL Clients

With your GraphQL server built, it’s now time to set up GraphQL on the client side. Very broadly, a client is just an application that communicates with a server. Because of the flexibility of GraphQL, there’s no prescription for how to build a client. You might be building apps for web browsers. You might be creating native applications for phones. You might be building a GraphQL service for a screen on your refrigerator. It also does not matter to the client in which language the service is written.

All you really need to send queries and mutations is the ability to send an HTTP request. When the service responds with some data, you can use it in your client no matter what that client is.

Using a GraphQL API

The easiest way to begin is just to make an HTTP request to your GraphQL endpoint. To test the server that we built in Chapter 5, make sure your service is running locally at http://localhost:4000/graphql. You can also find all of these samples running on CodeSandbox at the links found in the Chapter 6 repository.

fetch Requests

As you saw in Chapter 3, you can send requests to a GraphQL service by using cURL. All you need is a few different values:

  • A query: {totalPhotos, totalUsers}

  • A GraphQL endpoint: http://localhost:4000/graphql

  • A content type: Content-Type: application/json

From there, you send the cURL request directly from the terminal/command prompt using the POST method:

curl -X POST 
     -H "Content-Type: application/json" 
     --data '{ "query": "{totalUsers, totalPhotos}" }' 
     http://localhost:4000/graphql

If we send this request, we should see the correct results, {"data":{"totalUsers":7,"totalPhotos":4}}, as JSON data returned in the terminal. Your numbers for totalUsers and totalPhotos will reflect your current data. If your client is a shell script, you can start building that script with cURL.

Because we’re using cURL, we can use anything that sends an HTTP request. We could build a tiny client by using fetch, which will work in the browser:

var query = `{totalPhotos, totalUsers}`
var url = 'http://localhost:4000/graphql'

var opts = {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query })
}

fetch(url, opts)
  .then(res => res.json())
  .then(console.log)
  .catch(console.error)

After we fetch the data, we’ll see the expected result logged in the console:

{
  "data": {
    "totalPhotos": 4,
    "totalUsers": 7
  }
}

We can use the resulting data on the client to build applications. Let’s consider a basic example to see how we might list totalUsers and totalPhotos directly in the DOM:

fetch(url, opts)
  .then(res => res.json())
  .then(({data}) => `
        <p>photos: ${data.totalPhotos}</p>
        <p>users: ${data.totalUsers}</p>
  `)
  .then(text => document.body.innerHTML = text)
  .catch(console.error)

Instead of logging the results to the console, we use the data to build some HTML text. We can then take that text and write it directly to the document’s body. Be careful: it’s possible to overwrite anything that was in the body after the request is complete.

If you already know how to send HTTP requests using your favorite client, you already have the tools necessary to build a client application that communicates with any GraphQL API.

graphql-request

Though cURL and fetch work well, there are other frameworks that you can use to send GraphQL operations to an API. One of the most notable examples of this is graphql-request. graphql-request wraps fetch requests in a promise that can be used to make requests to the GraphQL server. It also handles the details of making the request and parsing the data for you.

To get started with graphql-request, you first need to install it:

npm install graphql-request

From there, you import and use the module as request. Be sure to keep the photo service running on port 4000:

import { request } from 'graphql-request'

var query = `
  query listUsers {
    allUsers {
      name
      avatar
    }
  }
`

request('http://localhost:4000/graphql', query)
    .then(console.log)
    .catch(console.error)

The request function takes in url and query, makes the request to the server, and returns the data in one line of code. The data returned is, as expected, a JSON response of all of the users:

{
  "allUsers": [
    { "name": "sharon adams", "avatar": "http://..." },
    { "name": "sarah ronau", "avatar": "http://..." },
    { "name": "paul young", "avatar": "http://..." },
  ]
}

We can begin using this data in our client straight away.

You can also send mutations with graphql-request:

import { request } from 'graphql-request'

var url = 'http://localhost:4000/graphql'

var mutation = `
    mutation populate($count: Int!) {
        addFakeUsers(count:$count) {
            id
            name
        }
    }
`

var variables = { count: 3 }

request(url, mutation, variables)
    .then(console.log)
    .catch(console.error)

The request function takes in the API URL, the mutation, and a third argument for variables. This is just a JavaScript object that passes in a field and value for the query variables. After we invoke request, we issue the addFakeUsers mutation.

Though graphql-request doesn’t offer any formal integration with UI libraries and frameworks, we can incorporate a library fairly simply. Let’s load some data into a React component using graphql-request, as demonstrated in Example 6-1.

Example 6-1. GraphQL Request and React
  import React from 'react'
  import ReactDOM from 'react-dom'
  import { request } from 'graphql-request'

  var url = 'http://localhost:4000/graphql'

  var query = `
    query listUsers {
      allUsers {
        avatar
        name
      }
    }
  `

  var mutation = `
      mutation populate($count: Int!) {
          addFakeUsers(count:$count) {
              githubLogin
          }
      }
  `

  const App = ({ users=[] }) =>
      <div>
          {users.map(user =>
              <div key={user.githubLogin}>
                  <img src={user.avatar} alt="" />
                  {user.name}
              </div>
          )}
          <button onClick={addUser}>Add User</button>
      </div>

  const render = ({ allUsers=[] }) =>
      ReactDOM.render(
          <App users={allUsers} />,
          document.getElementById('root')
      )

  const addUser = () =>
      request(url, mutation, {count:1})
          .then(requestAndRender)
          .catch(console.error)

  const requestAndRender = () =>
      request(url, query)
          .then(render)
          .catch(console.error)

  requestAndRender()

Our file starts with an import of both React and ReactDOM. We then create an App component. App maps over the users that are passed as props and creates div elements containing their avatar and username. The render function renders the App to the #root element and passes in allUsers as a property.

From there, requestAndRender calls request from graphql-request. This issues the query, receives the data, and then calls render, which provides the data to the App component.

This little app also handles mutations. In the App component, the button has an onClick event that calls the addUser function. When invoked, this sends the mutation and then calls requestAndRender to issue a new request for the services users and rerenders the <App /> with the new list of users.

So far, we’ve looked at a few different ways to begin building client apps using GraphQL. You can write shell scripts with cURL. You can build web pages with fetch. You can build apps a little faster with graphql-request. You could stop right there if you wanted to, but there are even more powerful GraphQL clients available. Let’s go for it.

Apollo Client

A huge benefit of using Representational State Transfer (REST) is the ease with which you can handle caching. With REST, you can save the response data from a request in a cache under the URL that was used to access that request. Caching done, no problem. Caching GraphQL is a little trickier. We don’t have a multitude of routes with a GraphQL API—everything is sent and received over a single endpoint, so we cannot simply save the data returned from a route under the URL that was used to request it.

To build a robust, performant application, we need a way to cache queries and their resulting objects. Having a localized caching solution is essential as we constantly strive to create fast, efficient apps. We could create something like this ourselves, or we could lean on one of the vetted clients that already exist.

The most prominent GraphQL client solutions available today are Relay and Apollo Client. Relay was open sourced by Facebook in 2015 at the same time as GraphQL. It brings together everything that Facebook learned about using GraphQL in production. Relay is compatible with React and React Native only, which means that there was an opportunity to create a GraphQL client to support developers who might not use React.

Enter Apollo Client. Brought to you by Meteor Development Group, Apollo Client is a community-driven project to build a flexible GraphQL client solution to handle tasks like caching, optimistic UI updates, and more. The team has created packages that supply bindings for React, Angular, Ember, Vue, iOS, and Android.

We’ve already been using several tools from the Apollo team on the server, but Apollo Client focuses specifically on sending and receiving requests from the client to the server. It handles the network requests with Apollo Link and handles all caching with Apollo Cache. Apollo Client then wraps the link and the cache and manages all interactions with the GraphQL service efficiently.

For the rest of the chapter, we take a closer look at Apollo Client. We’re going to be using React to build out our UI components, but we can apply many of the techniques described here to projects that use different libraries and frameworks.

Apollo Client with React

Since working with React is what led us to GraphQL in the first place, we have chosen React as the user interface library. We haven’t offered much explanation about React itself. It is a library that was created at Facebook that uses a component-based architecture to compose UIs. If you are a user of a different library and you never want to look at React again after this, that’s ok. The ideas presented in this next section are applicable to other UI frameworks.

Project Setup

In this chapter, we show you how to build a React app that interacts with a GraphQL service using Apollo Client. To begin, we need to scaffold the frontend of this project using create-react-app. create-react-app allows you to generate an entire React project without setting up any build configuration. If you haven’t used create-react-app before, you might need to install it:

npm install -g create-react-app

Once installed, you can create a React project anywhere on your computer with:

create-react-app photo-share-client

This command installs a new base React application in a folder named photo-share-client. It automatically adds and installs everything that you will need to get started building a React app. To start the application, navigate to the photo-share-client folder and run npm start. You’ll see your browser open to http://localhost:3000 where your React client application is running. Remember, you can find all of the files for this chapter in the repository at http://github.com/moonhighway/learning-graphql.

Configure Apollo Client

You’ll need to install a few packages to build a GraphQL client with Apollo tools. First, you’ll need graphql which includes the GraphQL language parser. Then you’ll need a package called apollo-boost. Apollo Boost includes the Apollo packages necessary for creating an Apollo Client and sending operations to that client. Finally, we’ll need react-apollo. React Apollo is an npm library that contains React components that we will use to construct a user interface with Apollo.

Let’s install these three packages at the same time:

npm install graphql apollo-boost react-apollo

Now we are ready to create our client. The ApolloClient constructor found in apollo-boost can be used to create our first client. Open the src/index.js file and replace the code in that file with the following:

import ApolloClient from 'apollo-boost'

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

Using the ApolloClient constructor, we’ve created a new client instance. The client is ready to handle all network communication with the GraphQL service hosted at http://localhost:4000/graphql. For example, we can use the client to send a query to the PhotoShare Service:

import ApolloClient, { gql } from 'apollo-boost'

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

const query = gql`
    {
        totalUsers
        totalPhotos
    }
`

client.query({query})
    .then(({ data }) => console.log('data', data))
    .catch(console.error)

This code uses the client to send a query for the total photo count and the total user count. To make this happen, we imported the gql function from apollo-boost. This function is a part of the graphql-tag package that was automatically included with apollo-boost. The gql function is used to parse a query into an AST or abstract syntax tree.

We can send the AST to the client by invoking client.query({query}). This method returns a promise. It sends the query as an HTTP request to our GraphQL service and resolves the data returned from that service. In the above example, we are logging the response to the console:

{ totalUsers: 4, totalPhotos: 7, Symbol(id): "ROOT_QUERY" }

GraphQL Service Should Be Running

Make sure that the GraphQL service is still running on http://localhost:4000 so that you can test the client connection to the server.

In addition to handling all network requests to our GraphQL service, the client also caches the responses locally in memory. At any point, we can take a look at the cache by invoking client.extract():

console.log('cache', client.extract())
client.query({query})
    .then(() => console.log('cache', client.extract()))
    .catch(console.error)

Here we have a look at the cache before the query is sent, and another look at it after the query has been resolved. We can see that we now have the results saved in a local object which is managed by the client:

    {
        ROOT_QUERY: {
            totalPhotos: 4,
            totalUsers: 7
        }
    }

The next time we send the client a query for this data, it will read it from the cache as opposed to sending another network request to our service. Apollo Client provides us with options to specify when, and how often, we should send HTTP requests over the network. We’ll cover those options later on in this chapter. For now, it is important to understand that Apollo Client is used to handle all network requests to our GraphQL service. Additionally, by default, it automatically caches the results locally and defers to the local cache to improve our applications performance.

To get started with react-apollo, all we need to do is create a client and add it to our user interface with a component called ApolloProvider. Replace the code found in the index.js file with the following:

import React from 'react'
import { render } from 'react-dom'
import App from './App'
import { ApolloProvider } from 'react-apollo'
import ApolloClient from 'apollo-boost'

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

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

This is all the code you will need to get started using Apollo with React. Here, we’ve created a client and then placed that client in React’s global scope with the help of a component called the ApolloProvider. Any child component wrapped by the ApolloProvider will have access to the client. That means that the <App /> component and any of its children are ready to receive data from our GraphQL service via Apollo Client.

The Query Component

Using Apollo Client, we need a way to handle queries to fetch data to load into our React UI. The Query component will take care of fetching data, handling loading state, and updating our UI. We can use the Query component anywhere within the ApolloProvider. The Query component sends a query using the client. Once resolved, the client will return the results that we’ll use to construct the user interface.

Open the src/App.js file and replace the code that is currently inside of this file with the following:

import React from 'react'
import Users from './Users'
import { gql } from 'apollo-boost'

export const ROOT_QUERY = gql`
    query allUsers {
        totalUsers
        allUsers {
            githubLogin
            name
            avatar
        }
    }
`

const App = () => <Users />

export default App

In the App component, we’ve created a query called ROOT_QUERY. Remember, one of the benefits of using GraphQL is to request everything you’ll need to construct your UI and receive all of that data in a single response. That means we are going to request both the totalUsers count and the allUsers array in a query that we’ve created in the root of our application. Using the gql function, we’ve converted our string query an AST object named ROOT_QUERY, and we’ve exported this object so that other components can use it.

At this point, you should see an error. This is because we’ve told the App to render a component that we have not created. Create a new file called src/Users.js and place this code inside of that file:

import React from 'react'
import { Query } from 'react-apollo'
import { ROOT_QUERY } from './App'

const Users = () =>
    <Query query={ROOT_QUERY}>
        {result =>
            <p>Users are loading: {result.loading ? "yes" : "no"}</p>
        }
    </Query>

export default Users

Now you should see the error clear, and the message “Users are loading: no” should be displayed in the browser window. Under the hood, the Query component is sending the ROOT_QUERY to our GraphQL service and caching the result locally. We obtain the result using a React technique called render props. Render props allow us to pass properties as function arguments to child components. Notice that we are obtaining the result from a function and returning a paragraph element.

The result contains more information than just the response data. It will tell us whether or not an operation is loading via the result.loading property. In the preceding example, we can tell the user whether or not the current query is loading.

Throttle the HTTP Request

Your network might be too fast to see more than a quick flicker of the loading property in the browser. You can use the Network tab in Chrome’s developer tools to throttle the HTTP request. In the developer tools, you’ll find a dropdown that has the “Online” option selected. Selecting “Slow 3G” from the dropdown will simulate a slower response. This will allow you to see the loading happen in the browser.

Once the data has loaded, it will be passed along with the result.

Instead of displaying “yes” or “no” when the client is loading data, we can display UI components instead. Let’s adjust the Users.js file:

const Users = () =>
    <Query query={ROOT_QUERY}>
        {({ data, loading }) => loading ?
            <p>loading users...</p> :
            <UserList count={data.totalUsers} users={data.allUsers} />
        }
    </Query>


const UserList = ({ count, users }) =>
    <div>
        <p>{count} Users</p>
        <ul>
            {users.map(user =>
                <UserListItem key={user.githubLogin}
                    name={user.name}
                    avatar={user.avatar} />
            )}
        </ul>
    </div>

const UserListItem = ({ name, avatar }) =>
    <li>
        <img src={avatar} width={48} height={48} alt="" />
        {name}
    </li>

If the client is loading the current query, we will display a “loading users…” message. If the data has been loaded, we will pass the total user count along with an array containing the name, githubLogin, and avatar of every user to the UserList component: exactly the data we asked for in our query. The UserList uses the result data to build the UI. It displays the count along with a list that displays the user’s avatar image alongside of their name.

The results object also has several utility functions for pagination, refetching, and polling. Let’s use the refetch function to refetch the list of users when we click a button:

const Users = () =>
    <Query query={ROOT_QUERY}>
        {({ data, loading, refetch }) => loading ?
            <p>loading users...</p> :
            <UserList count={data.totalUsers}
                users={data.allUsers}
                refetchUsers={refetch} />
        }
    </Query>

Here we’ve obtained a function that can be used to refetch the ROOT_QUERY or request the data from the server again. The refetch property is simply a function. We can pass it to the UserList where it can be added to a button click:

const UserList = ({ count, users, refetch }) =>
    <div>
        <p>{count} Users</p>
        <button onClick={() => refetch()}>Refetch</button>
        <ul>
            {users.map(user =>
                <UserListItem key={user.githubLogin}
                    name={user.name}
                    avatar={user.avatar} />
            )}
        </ul>
    </div>

In the UserList, we are using the refetch function to request the same root data from our GraphQL service. Whenever you click the “Refetch Users” button, another query will be sent to the GraphQL endpoint to refetch any data changes. This is one way to keep your user interface in sync with the data on the server.

Note

To test this, we can change the user data after the initial fetch. You can delete the users collection, delete user documents directly from MongoDB, or add fake users by sending a query with the server’s GraphQL Playground. When you change the data in the database, the “Refetch Users” button will need to be clicked in order to re-render the most up to date data in the browser.

Polling is another option that is available with the Query component. When we add the pollInterval prop to the Query component, data is automatically fetched over and over again based on a given interval:

<Query query={ROOT_QUERY} pollInterval={1000}>

Setting a pollInterval automatically refetches the data at a specified time. In this case, we will refetch the data from the server every second. Be careful when using polling as this code actually sends a new network request every second.

In addition to loading, data, and refetch, the response object has some additional options:

stopPolling

A function that stops polling

startPolling

A function that will start polling

fetchMore

A function that can be used to fetch the next page of data

Before we continue, remove any pollInterval properties from the Query component. We do not want polling to take place as we continue to iterate on this example.

The Mutation Component

When we want to send mutations to the GraphQL service, we can use the Mutation component. In the next example, we use this component to handle the addFakeUsers mutation. When we send this mutation, we write the new list of users directly to the cache.

To begin, let’s import the Mutation component and add a mutation to the Users.js file:

import { Query, Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'

...

const ADD_FAKE_USERS_MUTATION = gql`
    mutation addFakeUsers($count:Int!) {
        addFakeUsers(count:$count) {
            githubLogin
            name
            avatar
        }
    }
`

Once we have the mutation, we can use it in combination with the Mutation component. This component will pass a function to its children via render props. This function can be used to send the mutation when we are ready:

const UserList = ({ count, users, refetchUsers }) =>
    <div>
        <p>{count} Users</p>
        <button onClick={() => refetchUsers()}>Refetch Users</button>
        <Mutation mutation={ADD_FAKE_USERS_MUTATION} variables={{ count: 1 }}>
            {addFakeUsers =>
                <button onClick={addFakeUsers}>Add Fake Users</button>
            }
        </Mutation>
        <ul>
            {users.map(user =>
                <UserListItem key={user.githubLogin}
                    name={user.name}
                    avatar={user.avatar} />
            )}
        </ul>
    </div>

Just as we sent query as a prop to the Query component, we will send a mutation prop to the Mutation component. Notice also that we’re using the variables property. This will send the necessary query variables with the mutation. In this case, it sets the count to 1, which will cause the mutation to add one fake user at a time. The Mutation component uses a function, addFakeUsers, that will send the mutation once it has been invoked. When the user clicks the “Add Fake Users” button, the mutation will be sent to our API.

Currently, these users are being added to the database, but the only way to see the changes is to click the “Refetch Users” button. We can tell the Mutation component to refetch specific queries once the mutation has completed instead of waiting for our users to click a button:

<Mutation mutation={ADD_FAKE_USERS_MUTATION}
    variables={{ count: 1 }}
    refetchQueries={[{ query: ROOT_QUERY }]}>
    {addFakeUsers =>
        <button onClick={addFakeUsers}>Add Fake Users</button>
    }
</Mutation>

refetchQueries is a property that lets you specify which queries to refetch after sending a mutation. Simply place a list of objects that contain queries. Each of the query operations found in this list will refetch data after the mutation has completed.

Authorization

In Chapter 5, we built a mutation to authorize a user with GitHub. In the following section, we show you how to set up user authorization on the client side.

The process of authorizing a user involves several steps. The bold steps indicate the functionality we’ll add to the client:

Client

Redirects the user to GitHub with the client_id

User

Allows access to account information on GitHub for the client application

GitHub

Redirects back to the website with code: http://localhost:3000?code=XYZ

Client

Sends GraphQL Mutation authUser(code) with code

API

Requests a GitHub access_token with client_id, client_secret, and client_code

GitHub

Responds with access_token that can be used with future info requests

API

Request user info with access_token

GitHub

Responds with user info: name, github_login, avatar_url

API

Resolves authUser(code) mutation with AuthPayload, which contains a token and the user

Client

Saves token to send with future GraphQL requests

Authorizing the User

It is now time to authorize the user. To facilitate this example, we use React Router, which we install via npm: npm install react-router-dom.

Let’s modify our main <App /> component. We’ll incorporate the BrowserRouter, and we’ll add a new component, AuthorizedUser, that we can use to authorize users with GitHub:

import React from 'react'
import Users from './Users'
import { BrowserRouter } from 'react-router-dom'
import { gql } from 'apollo-boost'
import AuthorizedUser from './AuthorizedUser'

export const ROOT_QUERY = gql`
    query allUsers {
        totalUsers
        allUsers { ...userInfo }
        me { ...userInfo }
    }

    fragment userInfo on User {
        githubLogin
        name
        avatar
    }
`

const App = () =>
  <BrowserRouter>
    <div>
        <AuthorizedUser />
        <Users />
    </div>
  </BrowserRouter>

export default App

BrowserRouter wraps all of the other components that we want to render. We also will add a new AuthorizedUser component, which we will build in a new file. We should see an error until we add that component.

We’ve also modified the ROOT_QUERY to get it ready for authorization. We are now additionally asking for the me field, which returns information about the current user when someone is logged in. When a user is not logged in, this field will simply return null. Notice that we’ve added a fragment called userInfo to the query document. This allows us obtain the same information about a User in two places: the me field and the allUsers field.

The AuthorizedUser component should redirect the user to GitHub to request a code. That code should be passed back from GitHub to our app at http://localhost:3000.

In a new file called AuthorizedUser.js, let’s implement this process:

import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'

class AuthorizedUser extends Component {

    state = { signingIn: false }

    componentDidMount() {
        if (window.location.search.match(/code=/)) {
            this.setState({ signingIn: true })
            const code = window.location.search.replace("?code=", "")
            alert(code)
            this.props.history.replace('/')
        }
    }

    requestCode() {
      var clientID = <YOUR_GITHUB_CLIENT_ID>
      window.location =
        `https://github.com/login/oauth/authorize?client_id=${clientID}&scope=user`
    }

    render() {
        return (
          <button onClick={this.requestCode} disabled={this.state.signingIn}>
              Sign In with GitHub
          </button>
        )
    }
}

export default withRouter(AuthorizedUser)

The AuthorizedUser component renders a “Sign In with GitHub” button. Once clicked, this button will redirect the user to GitHub’s OAuth process. Once authorized, GitHub will pass a code back to the browser: http://localhost:3000?code=XYZGNARLYSENDABC. If the code is found in the query string, the component parses it from the window’s location and displays it in an alert box to the user before removing it with the history property that was sent to this component with React Router.

Instead of sending the user an alert with the GitHub code, we need to send it to the githubAuth mutation:

import { Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'
import { ROOT_QUERY } from './App'

const GITHUB_AUTH_MUTATION = gql`
    mutation githubAuth($code:String!) {
        githubAuth(code:$code) { token }
    }
`

The above mutation will be used to authorize the current user. All we need is the code. Let’s add this mutation to the render method of this component:

render() {
    return (
        <Mutation mutation={GITHUB_AUTH_MUTATION}
            update={this.authorizationComplete}
            refetchQueries={[{ query: ROOT_QUERY }]}>

            {mutation => {
                this.githubAuthMutation = mutation
                return (
                    <button
                        onClick={this.requestCode}
                        disabled={this.state.signingIn}>
                        Sign In with GitHub
                    </button>
                )
            }}

        </Mutation>
    )
}

The Mutation component is tied to the GITHUB_AUTH_MUTATION. Once completed, it will invoke the component’s authorizationComplete method and refetch the ROOT_QUERY. The mutation function has been added to the scope of the AuthorizedUser component by setting: this.githubAuthMutation = mutation. We can now invoke this this.githubAuthMutation() function when we are ready (when we have a code).

Instead of alerting the code, we will send it along with the mutation to authorize the current user. Once authorized, we will save the resulting token to localStorage and use the router’s history property to remove the code from the window’s location:

class AuthorizedUser extends Component {

    state = { signingIn: false }

    authorizationComplete = (cache, { data }) => {
        localStorage.setItem('token', data.githubAuth.token)
        this.props.history.replace('/')
        this.setState({ signingIn: false })
    }

    componentDidMount() {
        if (window.location.search.match(/code=/)) {
            this.setState({ signingIn: true })
            const code = window.location.search.replace("?code=", "")
            this.githubAuthMutation({ variables: {code} })
        }
    }

    ...

}

To start the authorization process, invoke this.githubAuthMutation() and add the code to the operation’s variables. Once complete, the authorizationComplete method will be called. The data passed to this method is the data that we selected in the mutation. It has a token. We’ll save the token locally and use React Router’s history to remove the code query string from the window’s location bar.

At this point, we will have signed in the current user with GitHub. The next step will be to make sure that we send this token along with every request in the HTTP headers.

Identifying the User

Our next task is to add a token to the authorization header for each request. Remember, the photo-share-api service that we created in the last chapter will identify users who pass an authorization token in the header. All we have to do is make sure any token saved to localStorage is sent along with every request sent to our GraphQL service.

Let’s modify the src/index.js file. We need to find the line where we create the Apollo Client and replace it with this code:

const client = new ApolloClient({
    uri: 'http://localhost:4000/graphql',
    request: operation => {
        operation.setContext(context => ({
            headers: {
                ...context.headers,
                authorization: localStorage.getItem('token')
            }
        }))
    }
})

We’ve now added a request method to our Apollo Client configuration. This method pass the details about every operation just before it is sent to the GraphQL service. Here we are setting the context of every operation to include an authorization header that contains the token saved to local storage. Don’t worry, if we don’t have a token saved the value of this header will simply be null and our service will assume that there a user has not been authorized.

Now that we’ve added the authorization token to every header, our me field should return data about the current user. Let’s display that data in our UI. Find the render method in the AuthorizedUser component and replace it with this code:

render() {
    return (
        <Mutation
            mutation={GITHUB_AUTH_MUTATION}
            update={this.authorizationComplete}
            refetchQueries={[{ query: ROOT_QUERY }]}>
            {mutation => {
                this.githubAuthMutation = mutation
                return (
                    <Me signingIn={this.state.signingIn}
                        requestCode={this.requestCode}
                        logout={() => localStorage.removeItem('token')} />
                )
            }}
        </Mutation>
    )
}

Instead of rendering a button, this Mutation component now renders a component called Me. The Me component will either display information about the current user who is logged in or the authorize button. It will need to know whether or not the user is currently in the process of signing in. It also needs to access the requestCode methods of the AuthorizedUser component. Finally, we need to provide a function that can log the current user out. For now, we’ll just remove the token from localStorage when the user logs out. All of these values have been passed down to the Me component as properties.

It’s now time to create the Me component. Add the following code above the declaration of the AuthorizedUser component:

const Me = ({ logout, requestCode, signingIn }) =>
    <Query query={ROOT_QUERY}>
        {({ loading, data }) => data.me ?
            <CurrentUser {...data.me} logout={logout} /> :
            loading ?
                <p>loading... </p> :
                <button
                    onClick={requestCode}
                    disabled={signingIn}>
                        Sign In with GitHub
                </button>
        }
    </Query>

const CurrentUser = ({ name, avatar, logout }) =>
    <div>
        <img src={avatar} width={48} height={48} alt="" />
        <h1>{name}</h1>
        <button onClick={logout}>logout</button>
    </div>

The Me component renders a Query component to obtain the data about the current user from the ROOT_QUERY. If there is a token, the me field in the ROOT_QUERY will not be null. Within the query component, we check to see if data.me is null. If there is data under this field, we will display the CurrentUser component and pass the data about the current user to this component as properties. The code {...data.me} uses the spread operator to pass all of the fields to the CurrentUser component as individual properties. Additionally, the logout function is passed to the CurrentUser component. When the user clicks the logout button, this function will be invoked and their token removed.

Working with the Cache

As developers, we’re in the network request minimization business. We don’t want our users to have to make extraneous requests. In order to minimize the number of network requests that our apps send, we can dig deeper into how to customize the Apollo Cache.

Fetch Policies

By default, Apollo Client stores data in a local JavaScript variable. Every time we create a client, a cache is created for us. Every time we send an operation, the response is cached locally. The fetchPolicy tells Apollo Client where to look for data to resolve an operation: either the local cache or a network request. The default fetchPolicy is cache-first. This means that the client will look locally in the cache for data to resolve the operation. If the client can resolve the operation without sending a network request, it will do so. However, if data to resolve the query is not in the cache then the client will send a network request to the GraphQL service.

Another type of fetchPolicy is cache-only. This policy tells the client to only look in the cache and never send a network request. If the data to fulfill the query does not exist in the cache, then an error will be thrown.

Take a look at src/Users.js, and find the Query inside the Users component. We can change the fetch policy of individual queries simply by adding the fetchPolicy property:

<Query query={{ query: ROOT_QUERY }} fetchPolicy="cache-only">

At present, if we set the policy for this Query to cache-only and refresh the browser, we should see an error because Apollo Client is only looking in the cache for the data to resolve our query and that data is not present when the app starts. To clear the error, change the fetch policy to cache-and-network:

<Query query={{ query: ROOT_QUERY }} fetchPolicy="cache-and-network">

The application works again. The cache-and-network policy always resolves the query immediately from the cache and additionally sends a network request to get the latest data. If the local cache does not exist, as is the case when the app starts, this policy will simply retrieve the data from the network. Other policies include:

network-only

Always sends a network request to resolve a query

no-cache

Always sends a network request to resolve the data and it doesn’t cache the resulting response

Persisting The Cache

It is possible to save the cache locally on the client. This unlocks the power of the cache-first policy, because the cache will already exist when the user returns to the application. In this case, the cache-first policy will immediately resolve the data from the existing local cache and not send a request to the network at all.

To save cache data locally, we’ll need to install an npm package:

npm install apollo-cache-persist

The apollo-cache-persist package contains a function that enhance the cache by saving it to a local store whenever it changes. To implement cache persistance, we’ll need to create our own cache object and add it to the client when we configure our application.

Add the following code to the src/index.js file:

import ApolloClient, { InMemoryCache } from 'apollo-boost'
import { persistCache } from 'apollo-cache-persist'

const cache = new InMemoryCache()
persistCache({
    cache,
    storage: localStorage
})

const client = new ApolloClient({
    cache,

    ...

})

First, we’ve created our own cache instance using the InMemoryCache constructor provided with apollo-boost. Next, we imported the persistCache method from apollo-cache-persist. Using InMemoryCache, we create a new cache instance and send it to the persistCache method along with a storage location. We’ve chosen to save the cache in the browser window’s localStorage store. This means that once we start our application, we should see the value of our cache saved to our store. You can check for it by adding the following syntax:

console.log(localStorage['apollo-cache-persist'])

The next step is to check localStorage on startup to see if we already have a cache saved. If we do, then we’ll want to initialize our local cache with that data before creating the client:

const cache = new InMemoryCache()
persistCache({
    cache,
    storage: localStorage
})

if (localStorage['apollo-cache-persist']) {
    let cacheData = JSON.parse(localStorage['apollo-cache-persist'])
    cache.restore(cacheData)
}

Now our application will load any cached data before it starts. If we do have data saved under the key apollo-cache-persist, then we’ll use the cache.restore(cacheData) method to add it to the cache instance.

We’ve successfully minimized the number of network requests to our service simply by using Apollo Client’s cache effectively. In the next section, we will learn about how we can write data directly to the local cache.

Updating the Cache

The Query component is capable of reading directly from the cache. That’s what makes a fetch policy like cache-only possible. We are also able to interact directly with the Apollo Cache. We can read current data from the cache or write data directly to the cache. Every time we change data stored in the cache, react-apollo detects that change and re-renders all of the effected components. All we have to do is change the cache and the UI will automatically update to match the change.

Data is read from the Apollo Cache using GraphQL. You read queries. Data is written to the Apollo Cache using GraphQL, you write data to queries. Consider the ROOT_QUERY that is located in src/App.js:

export const ROOT_QUERY = gql`
    query allUsers {
        totalUsers
        allUsers { ...userInfo }
        me { ...userInfo }
    }

    fragment userInfo on User {
        githubLogin
        name
        avatar
    }
`

This query has three fields in its selection set: totalUsers, allUsers, and me. We can read any data that we currently have stored in our cache using the cache.readQuery method:

let { totalUsers, allUsers, me }  = cache.readQuery({ query: ROOT_QUERY })

In this line of code, we’ve obtained the values for totalUsers, allUsers, and me that were stored in the cache.

We can also write data directly to the totalUsers, allUsers, and me fields of the ROOT_QUERY using the cache.writeQuery method:

cache.writeQuery({
    query: ROOT_QUERY,
    data: {
        me: null,
        allUsers: [],
        totalUsers: 0
    }
})

In this example, we are clearing all of the data from our cache and resetting default values for all of the fields in the ROOT_QUERY. Because we are using react-apollo, this change would trigger a UI update and clear the entire list of users from the current DOM.

A good place to write data directly to the cache is inside of the logout function in the AuthorizedUser component. At present this function is removing the user’s token, but the UI does not update until the “Refetch” button has been clicked or the browser is refreshed. To improve this feature, we will clear out the current user from the cache directly when the user logs out.

First we need to make sure that this component has access to the client in its props. One of the fastest ways to pass this property is to use the withApollo higher order component. This will add the client to the AuthorizedUser component’s properties. Since this component already uses the withRouter higher order component, we will use the compose function to make sure that the AuthorizedUser component is wrapped with both higher order components:

import { Query, Mutation, withApollo, compose } from 'react-apollo'

class AuthorizedUser extends Component {
    ...
}

export default compose(withApollo, withRouter)(AuthorizedUser)

Using compose, we assemble the withApollo and withRouter functions into a single function. withRouter adds the Router’s history to the properties, and withApollo adds Apollo Client to the properties.

This means that we can access Apollo Client in our logout method and use it to remove the details about the current user from the cache:

logout = () => {
    localStorage.removeItem('token')
    let data = this.props.client.readQuery({ query: ROOT_QUERY })
    data.me = null
    this.props.client.writeQuery({ query: ROOT_QUERY, data })
}

The above code not only removes the current user’s token from localStorage, it clears the me field for the current user saved in the cache. Now when users log out, they will see the “Sign In with GitHub” button immediately without having to refresh the browser. This button is rendered only when the ROOT_QUERY doesn’t have any values for me.

Another place that we can improve our application thorough working directly with the cache is in the src/Users.js file. Currently, when we click the “Add Fake User” button, a mutation is sent to the GraphQL service. The Mutation component that renders the “Add Fake User” button contains the following property:

refetchQueries={[{ query: ROOT_QUERY }]}

This property tells the client to send an additional query to our service once the mutation has completed. However, we are already receiving a list of the new fake users in the response of the mutation itself:

mutation addFakeUsers($count:Int!) {
    addFakeUsers(count:$count) {
        githubLogin
        name
        avatar
    }
}

Since we already have a list of the new fake users, there is no need to go back to the server for the same information. What we need to do is obtain this new list of users in the mutation’s response and add it directly to the cache. Once the cache changes, the UI will follow.

Find the Mutation component in the Users.js file that handles the addFakeUsers mutation and replace the refetchQueries with an update property:

<Mutation mutation={ADD_FAKE_USERS_MUTATION}
    variables={{ count: 1 }}
    update={updateUserCache}>
    {addFakeUsers =>
        <button onClick={addFakeUsers}>Add Fake User</button>
    }
</Mutation>

Now, when the mutation has completed, the response data will be sent to a function called updateUserCache:

const updateUserCache = (cache, { data:{ addFakeUsers } }) => {
    let data = cache.readQuery({ query: ROOT_QUERY })
    data.totalUsers += addFakeUsers.length
    data.allUsers = [
        ...data.allUsers,
        ...addFakeUsers
    ]
    cache.writeQuery({ query: ROOT_QUERY, data })
}

When the Mutation component invokes the updateUserCache function, it sends the cache and the data that has been returned in the mutation’s response.

We want to add the fake users to the current cache, so we’ll read the data that is already in the cache using cache.readQuery({ query: ROOT_QUERY }) and add to it. First, we’ll increment the total users, data.totalUsers += addFakeUsers.length. Then, we’ll concatenate the current list of users with the fake users that we’ve received from the mutation. Now that the current data has been changed, it can be written back to the cache using cache.writeQuery({ query: ROOT_QUERY, data }). Replacing the data in the cache will cause the UI to update and display the new fake user.

At this point, we have completed the first version of the User portion of our app. We can list all users, add fake users, and sign in with GitHub. We have built a full stack GraphQL application using Apollo Server and Apollo Client. The Query and Mutation components are tools that we can use to quickly begin developing clients with Apollo Client and React.

In Chapter 7, we see how we can incorporate subscriptions and file uploading into the PhotoShare application. We also discuss emerging tools in the GraphQL ecosystem that you can incorporate into your projects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset