ReactとApolloを使ってGithub GraphQL APIを試してみる

[Livesenseその2 Advent Calendar 2016 \- Qiita](http://qiita.com/advent-calendar/2016/livesense_2) の24日目の記事です。書いてる今は既に25日ですが(；・∀・)

[Apollo](http://dev.apollodata.com/)というGraphQLクライアントライブラリを使ってGraphQL APIを利用するための[サンプルコード](https://github.com/bananaumai/react-apollo-sample)を解説します。

というのも、Apolloにもサンプルコードがあるのですが、自分でサーバー立てたりする必要もあり、中々面倒でした。ですので、手軽にGithubのGraphQL APIを使うことで、雰囲気を掴めるかと思います。

# GraphQLとは？

[GraphQL入門 \- 使いたくなるGraphQL \- Qiita](http://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad) にこの辺は書いてありますので、ご参照ください。

# ソースコード

https://github.com/bananaumai/react-apollo-sample

動かし方はInstallationに書いてあるとおりなのですが、前提として、前述のGithubの[Early Access Program](https://developer.github.com/early-access)にサインアップしておき、[Creating an access token guide](https://help.github.com/articles/creating-an-access-token-for-command-line-use/)に従ってアクセストークンを取得しておく必要があります。このアクセストークンをconfig.secret.jsに仕込んでください。

本コードはサンプルなので直接Githubのアクセストークンをクライントコードに含むような作りにしていますので、これ自体をWEBにアップしたり、プロダクションで同様の書き方をするのはしないようにしてください。

# Apolloについて

http://dev.apollodata.com/

ApolloはGraphQLのクライアントライブラリで今回はApolloが提供するJavaScript用の[apollo-client](https://github.com/apollostack/apollo-client)を利用しています(JavaScript以外にもSwiftやAndroid用のクライアントもあります)。また、サンプルではReactを使っていますが、react用のバインディングも提供されており、ReactのComponentとGraphQLをシームレスに繋ぐことができます。

GraphQLのReact用クライアントとしては[Relay](https://facebook.github.io/relay/)というfacebookが開発しているクライアントライブラリもあるのですが、RelayはQueryの形式が標準的なGraphQLのそれと異なっているので、こちらをApolloを使うことにしました。

# サンプルコードの解説

## GraphQLクエリ

GraphQLのクエリが以下の部分です。

```js
import gql from 'graphql-tag'
 :
const RepositorySearchQuery = gql`
  query Search($q: String!) {
    search(query: $q, first: 10, type: REPOSITORY) {
      edges {
        node {
          ... on Repository {
            id,
            name,
            url
          }
        }
      }
    }
  }
`
```

gqlはGraphQLクエリを表現するためのapolloが提供するライブラリの関数で、クエリ本体は`quer Search($q: String!) {...}`の部分になります。上の記述、イメージとしてはSQL自体を直接書いているのと同じことです。この部分はGraphQLのGUIクライアントに直接食わせることもできるので、実際にやってみると以下のような結果になります。

![image](https://qiita-image-store.s3.amazonaws.com/0/5811/f16a37d3-f26d-f4b8-9a28-4575c3e94dfc.png)

## APIへの接続用のclient

```js

import ApolloClient, { createNetworkInterface } from 'apollo-client'
  :
const networkInterface = createNetworkInterface({ uri: 'https://api.github.com/graphql' }) // (1)
networkInterface.use([{ // (2)
  applyMiddleware(req, next) {
    if (!req.options.headers) {
      req.options.headers = {
    }
    req.options.headers.authorization = `bearer ${githubToken}`
    next()
  }
}])
const client = new ApolloClient({ // (1)
  networkInterface: networkInterface,
})
```

APIへの接続クライアントは上記のように作成します。

createNetworkInterfaceというヘルパー関数でNetworkInterfaceオブジェクトを生成し、それをclientに渡します(1)。

createNetworkIntefaceは、デフォルトで同一ドメインの`/graphql`にアクセスするNetworkIntefaceを生成するので、今回のように別ドメインのAPIに接続する場合や、GraphQLのパスが`/graphql`以外の場合は、`uri`オプションを指定する必要があります。

また、Github APIのようにHTTPリクエストに`Authorization`ヘッダーが必要な場合は、NetworkInterfaceのuseメソッドを通じて、HTTPリクエストヘッダにAuthorizationヘッダを追加するためのミドルウェアを追加します(2)。（[express](http://expressjs.com/ja/)のように、NetworkIntefaceはuseメソッドを使って、HTTPリクエストをフックするためのミドルウェアを重ねることができます）

## React ComponentとGraphQLを連携させる


```js

import { ApolloProvider, graphql } from 'react-apollo'
  :
class Repositories extends Component { // (3)
  render() {
    let repos = []
    let h1Text = `Searching For ${this.props.query} ...`

    if (this.props.data && this.props.data.search) {  // (5)
      h1Text = `Repositories about ${this.props.query} are`
      repos = this.props.data.search.edges.map(edge => ({
        key: edge.node.id,
        name: edge.node.name,
        url: edge.node.url
      }))
    }

    return (
      <div>
        <h1>{h1Text}</h1>
        <ul>
          {repos.map(repo => <Repository key={repo.key} name={repo.name} url={repo.url} />)}
        </ul>
      </div>
    )    
  }
}

class App extends Component {
  :
  :
  handleFormSubmit(evnt) {
    evnt.preventDefault()

    const RepositoriesWithQuery = graphql(RepositorySearchQuery, {  // (4)
      options: {variables: { q: this.state.q }}
    })(Repositories)

    this.setState({
      repositories: <RepositoriesWithQuery query={this.state.q} />
    })

  }
  :
  render() {
    return (
      <div>
        <SearchForm onSubmit={evnt => this.handleFormSubmit(evnt)} onChange={evnt => this.handleTextChange(evnt)} />
        <ApolloProvider client={client}> // (6)
          {this.state.repositories}
        </ApolloProvider>
      </div>
    )
  }
}
```

(3)のRepositoriesが今回GraphQLと紐付けるReact Componentです。

これを(4)のようにgraphql関数（react-apolloが提供するヘルパー関数）を通じて、GraphQLクエリーと結びつけます。具体的には、graphql関数が返す結果もReactコンポーネントとなっており、このComponetには、GraphQLによってAPIから取得したデータがpropsを通じて伝搬されるようになります[^1]。(5)にあるように、`props.data`を通じてGraphQLの結果が渡されることになります。`props.data`以下にはGraphQL APIから返されるJSONの構造がそのまま入っていますので[^2]、GUIクライアントのキャプチャしたものと同じ構造で欲しいデータにアクセスすることができます。また、GraphQLの取得は非同期に行われるため、コンポーネントの初回レンダリングタイミングとずれるので、上記のようにdataの有無によって条件分岐しています（もうすこしうまい書き方あるかと思いますが、サンプルコードなのでまぁ）。

最後に(6)。ライブラリ利用者から見ればおまじないのようなもので、GraphQLと結びつけるコンポーネントはApolloProviderというreact-apolloが提供するコンポーネントの下に配置する必要があります。（そうしないと正しく結果を取得してくれない）

多分こんな感じで動くようになる、、、はずです。

# 最後に

あまり日本語ではGraphQL関連の記事は多くないので、あまり盛り上がっていないとは思うのですが、本内容等について誤りやおかしな解釈があればご指摘いただければ幸いです。GraphQLが日本でももう少し普及するように今後も記事を書いていきたいと思う年末となりました。


[^1]: FunctionalComponentは結びつけることができませんでした。
[^2]: 実際には、GraphQLの結果JSONの情報プラス幾つかのメタデータや関数も`props.data`に含まれます。また、この`props.data`の構造を変更するためのオプションをgraphql関数に渡すことができます。詳しくは[Apolloのドキュメント](http://dev.apollodata.com/react/queries.html#graphql-props-option)をご参照ください。