Graphcool、React、Apolloを使って、GraphQLのQueryとMutationを実装する

## はじめに
[こちらの記事](https://qiita.com/yasuhiro-yamada/items/70241a7713650b0738cf)と内容が一部被っていますが、前回の様にGraphcool自体の説明は省き、Graphcoolを用いて、create-react-appで作ったReactアプリにおけるGraphQLのQueryとMutationの実装をなるべく詳細に説明したいと思います。

本アプリでは、次のような画面を作成して、簡単な記事情報の一覧と新規投稿の保存までが行えることを要件とします。
（取り敢えず、GraphQLの機能を実感する為に作ったものなので、あまり細かいエラーハンドリングやプロダクトに向けた最適化などは行っていません…）

### 完成イメージ
![スクリーンショット-2018-03-26-2.38.44.png](https://qiita-image-store.s3.amazonaws.com/0/199911/32dfbcba-ffe9-203f-035c-c91e4ccc4e15.png)

## 実行環境
- Mac OS Sierra: v10.12.6
- node: v8.2.0
- npm: v5.3.0

## 事前準備
### create-react-appのインストール

```sh
npm install -g create-react-app
```

### graphcool（GraphcoolのCLI）のインストール

```sh
npm install -g graphcool
```

### Graphcoolへのログイン（ローカル環境の認証）
Graphcoolアカウントが作成されていなければ、[公式サイト](https://console.graph.cool/login)からSign Upしてアカウントを作成して、次のコマンドを実行します。

```
graphcool login
```

loginに成功すると、ホームディレクトリの直下に`.graphcoolrc`が自動生成されます。

## Reactアプリの作成〜起動確認

```sh
# ホームディレクトリに移動
cd ~/
# `react-graphcool-sample`ディレクトリを作成し、Reactアプリの土台を作る
create-react-app react-graphcool-sample
# react-graphcool-sampleのルートに移動
cd react-graphcool-sample/
# Reactアプリの起動を確認
yarn start
```

## Graphcool環境の構築
Reactアプリの起動まで確認ができたら、別のターミナルを立ち上げて、 `react-graphcool-sample`のルートに移動し、`graphcool init`コマンドを用いて、 **server**という名前のディレクトリの内部に、Graphcoolを利用するためのファイルを作成します。
（`server`ではなく、任意の名前でも構いません）

```sh
cd ~/react-graphcool-sample/
graphcool init server
```

主に、次の３点が作成されます。

- graphcool.yml （ Graphcoolを利用するための設定ファイル。作成した型定義ファイルやserverless functionの参照、パーミッションの指定など）
- types.graphql （GraphQLのスキーマ定義ファイル）
- src/（serverless functionが格納されたディレクトリ）

### types.graphql（スキーマ定義）
types.graphql を編集して、次の`Post`という型の定義を追加します。（元々あった`User`の型などはコメントアウトして、Postの型だけが残るようにします）

```react-graphcool-sample/server/types.graphql
type Post @model {
  id: ID! @isUnique
  title: String!
  content: String
}
```

`type`で型の宣言を行い、各フィールドの`:`の後ろに続く`ID`や`String`でそれらのフィールドが何の型かを示しています。
`!`はNot Nullとなることを指定します。
その他、これらの型やスキーマについて、詳しくは[GraphQLの公式ページ](http://graphql.org/learn/schema/)をご参照ください。

また、`@`が付いているものはDirectiveと呼ばれるもので、フィールドに対して特殊な設定をします。（上記例では、`id`フィールドに格納される値をユニークなものとする）
こちらも詳しくは公式の[Directivesの項](http://graphql.org/learn/queries/#directives)などをご参照ください。

### graphcool.yml（パーミッションの指定など）
今回はserverless functionは使わないので、 次のものだけが残るようにして、他はコメントアウトします。

```react-graphcool-sample/server/graphcool.yml
types: ./types.graphql
permissions:
  - operation: "*"
```

### /src
react-graphcool-sample/server/src以下は、`src`ディレクトリごと削除します。

### graphcool deploy
`graphcool init`によって作成されたディレクトリに移動し、`graphcool deploy`コマンドを実行して、上記の設定をGraphcoolのサービスにデプロイします。
このコマンドは、スキーマなどに変更があった都度、同じコマンドを実行して変更を適用する必要があります。

```sh
# serverディレクトリに移動
cd server
graphcool deploy
```

`graphcool deploy`コマンドを実行すると、次の３つの質問をされます。
はじめの質問で、Shared Clusters（フリーで使えるクラウドのホスティングサービス）の指定を求められますので、いずれかのリージョンを指定します。
特にこだわりなければ、デフォルトの選択肢のままenterキーを押して進めましょう。

```sh
? Please choose the cluster you want to deploy to (Use arrow keys)

  Shared Clusters:
❯ shared-eu-west-1 
  shared-ap-northeast-1 
  shared-us-west-2 
```

次に、`target name`を聞かれますが、こちらも特にこだわりない場合は、enterキーを押せば、デフォルトの`prod`が適用されます。

```sh
? Please choose the target name (prod) 
```

続いて、`service name`を聞かれますが、こちらはデフォルトではディレクトリ名が適用されます。

```sh
? Please choose the service name (server) 
```

デプロイに成功すると、クラウドのコンソールから該当のプロジェクトを確認することができます。
確認用URLは、`https://console.graph.cool/`に続いて、`graphcool init`によって作成したディレクトリ名を指定します。
上記の例では、`https://console.graph.cool/server/`となります。
（コンソールにアクセスするには予め、Graphcoolにログインしている必要があります)

こちらのコンソールからデータの追加、エンドポイントの確認、プロジェクトの削除などができます。

また、デプロイに成功すると、`graphcool init`によって作成したディレクトリ内に、下記のような、Graphcoolへの接続用設定ファイルが生成されます。

```react-graphcool-sample/server/.graphcoolrc
targets:
  prod: shared-eu-west-1/xxxxxxxxxxxxxxxxxxxxxxxx
  default: prod
```

`xxxxxxxxxxxxxxxxxxxxxxxx`の部分はProject IDを示しており、次回以降の`graphcool deploy`実行時は、こちらの情報をもとに、Graphcoolのサービスに接続しにいきます。（Project IDもコンソールから確認が可能です）

### エンドポイントの取得
デプロイ実行完了時のメッセージの最後に表示される、`Simple API`の値を、後に続くReactアプリからのAPI接続設定に使いますので、手元に控えておきましょう。
（忘れても、`server`ディレクトリ内で`graphcool info`をたたけば、次の様にエンドポイントを確認することができます）

```
$ graphcool info

Service Name   Cluster / Service ID
────────────── ────────────────────────────────────────────
server   shared-eu-west-1/xxx

API:           Endpoint:
────────────── ────────────────────────────────────────────────────────────
  Simple API:        https://api.graph.cool/simple/v1/xxxxx
  Relay API:         https://api.graph.cool/relay/v1/xxxxx
  Subscriptions API: wss://subscriptions.graph.cool/v1/xxxxx
```

## Apolloの組み込み〜接続設定
GraphQL APIへ接続するためのクライアントライブラリであるApolloをインストールし、Reactアプリに組み込んで、APIへの接続設定を行います。

### Apolloインストール
次の4つのパッケージのインストールを行いましょう。

```sh
yarn add apollo-client-preset react-apollo graphql-tag graphql
```

パッケージのインストール後、Reactアプリで使用できるように、次の記述を`src/index.js`の先頭に追加します。

```react-graphcool-sample/src/index.js
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { HttpLink, InMemoryCache } from "apollo-client-preset";
```

### API接続設定
続いて次のコードによって、API接続情報を持ったApolloClientインスタンスを生成します。`GRAPHQL_ENDPOINT`には、先のGraphcoolデプロイ完了時に控えておいた`Simple API`の値を代入します。

```react-graphcool-sample/src/index.js
const GRAPHQL_ENDPOINT =  ""; // Simple APIの値を代入

if (!GRAPHQL_ENDPOINT) {
  throw Error("GRAPHQL_ENDPOINTが設定されていません。");
}
const client = new ApolloClient({
  link: new HttpLink({
    uri: GRAPHQL_ENDPOINT
  }),
  cache: new InMemoryCache()
});
```

上記インスタンスを`<ApolloProvider />`のPropsに渡し、`App`Componentをラップした`ApolloApp` Componentを最終的にレンダリングします。

```react-graphcool-sample/src/index.js
const ApolloApp = (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

ReactDOM.render(ApolloApp, document.getElementById("root"));
registerServiceWorker();

```

## React Componentの作成
記事情報を一覧し、投稿するためのUIとして、Componentを作成していきます。

srcディレクトリ直下にcomponentsディレクトリを作成し、その中にComponentを格納します。
（App Componentもcomponentsディレクトリに移します）

├ public
├ server
└ src
　└ components
　　├ App.js・・・PostListおよびPostFormをレンダリングする
　　├ Post.js・・・単体の記事情報（タイトルおよび内容）をレンダリングする
　　├ PostList.js・・・Queryによって全記事を取得し、`<Post />`で各記事情報をレンダリングする
　　└ PostForm.js・・・Mutationによって新規記事情報を追加保存する

### Post（記事情報表示）
Propsの型チェックを行うために、prop-typesのライブラリをインストールします。

```
yarn add -D prop-types
```

``` react-graphcool-sample/src/components/Post.js
import React, { Component } from "react";
import PropTypes from "prop-types";

class Post extends Component {
  render() {
    return (
      <dl>
        <dt>{this.props.post.title}</dt>
        <dd>{this.props.post.content}</dd>
      </dl>
    );
  }
}

Post.propTypes = {
  post: PropTypes.shape({
    id: PropTypes.string,
    title: PropTypes.string,
    content: PropTypes.string
  })
};

export default Post;

```

### PostList（記事情報取得＆一覧）
GraphQLから全記事データを取得するために、次のquery文を書きます。

```
  query {
    allPosts {
      id
      title
      content
    }
  }
```

こちらのquery文の実行結果については、ブラウザでコンソールにアクセスまたは`graphcool init`によって作成したディレクトリ内で`graphcool playground`コマンドを実行することによって、`Playground`を開いて試すことができます。

<img width="978" alt="スクリーンショット 2018-03-26 2.29.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/199911/5235c712-ab3c-f279-63b5-2cb0b6ec166e.png">


Query APIに関して、記事IDを指定して単体の記事情報を取得するなど、もっと詳しく知りたい場合は[こちら](https://www.graph.cool/docs/reference/graphql-api/query-api-nia9nushae/)をご参照ください。

`gql`はreact-apolloのhelper関数で、文字列をパースして、GraphQLの操作を行うために用います。

``` react-graphcool-sample/src/components/PostList.js
import React, { Component, Fragment } from "react";
import Post from "./Post";

import gql from "graphql-tag";
import { graphql } from "react-apollo";

const ALL_POSTS_QUERY = gql`
  query {
    allPosts {
      id
      title
      content
    }
  }
`;

class PostList extends Component {
  render() {
    if (this.props.allPostsQuery && this.props.allPostsQuery.loading) {
      return <p>データを読み込み中</p>;
    }

    if (this.props.allPostsQuery && this.props.allPostsQuery.error) {
      return <p>エラーが発生しました。</p>;
    }

    const allPosts = this.props.allPostsQuery.allPosts;
    if (allPosts.length === 0) {
      return <p>投稿がありません。</p>;
    }

    return (
      <Fragment>
        {allPosts.map(post => <Post key={post.id} post={post} />)}
      </Fragment>
    );
  }
}

export default graphql(ALL_POSTS_QUERY, { name: "allPostsQuery" })(PostList);

```

graphql関数（react-apolloのヘルパー関数）は、パースしたquery（ALL_POSTS_QUERY）を第一引数に渡し、第二引数にGraphQL APIから取得したデータが格納されるProps名（`allPostsQuery`）を指定したオブジェクトを渡し、Componentを引数に取って、拡張されたComponentを返す関数（HoC）を返します。

つまり、`graphql(ALL_POSTS_QUERY, { name: "allPostsQuery" })`がHoCとなり、PostListを引数に取って、拡張したPostListを最終的にexportしています。

また、ルートComponentである`App`が`ApolloProvider`によってラップされているので、Appの子ComponentであるPostListにProps（`props.allPostsQuery`）を通じてGraphQL APIから取得したデータが伝わります。

props.allPostsQuery以下にJSON形式で取得結果のデータが入っています。
データ取得開始から完了までの間は`loading`の値が`true`に、データの取得に失敗した場合は`error`の値にErrorオブジェクトが格納されているので、render()の最初の方の処理に、これらのケースのハンドリングを行なっています。

### PostForm.js（記事追加投稿）
PostListでは`query`を用いてデータを取得する処理を書きましたが、今度はデータを書き込む処理を書くために、`mutation`を用います。

入力フォームからのデータを変数として、GraphQL APIに渡す必要がありますが、次のように、`$`をつけて変数宣言することでmutationの引数に変数を渡せます。

さらに、mutationの引数に渡った変数は`createPost`の引数に渡され、`createPost`の実行（データの書き込み）が完了すると、idを返します。

```js
  mutation createPostMutation($title: String!, $content: String) {
    createPost(title: $title, content: $content) {
      id
    }
  }
```

mutationに変数に値を格納するため、記事投稿用の入力フォームを作成します。
２つの入力欄を設けて、１つはタイトル、もう片方は内容を入力するためのUIとし、それぞれ`<input>`、`<textarea>`を`<form>`内に配置して、レンダリングします。

それらのフォームの入力値を取得できるよう、入力される度に`onChange`にて`setState()`を呼び出し、入力値をeventオブジェクトから取り出し、stateに保存させます。

```react-graphcool-sample/src/components/PostForm.js
import React, { Component } from "react";
import gql from "graphql-tag";
import { graphql } from "react-apollo";

class PostForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      content: "",
      title: "",
      isSending: false
    };
  }

  createPost = async e => {
    e.preventDefault();
    this.setState({ isSending: true });
    const { title, content } = this.state;
    try {
      await this.props.createPostMutation({
        variables: {
          title,
          content
        }
      });
      this.setState({
        content: "",
        title: "",
        isSending: false
      });
    } catch (err) {
      console.log(err);
    }
  };

  render() {
    return (
      <form>
        <div>
          <input
            id="title"
            type="text"
            value={this.state.title}
            placeholder="タイトルを入力"
            onChange={e => this.setState({ title: e.target.value })}
            style={{ width: "500px" }}
          />
        </div>
        <div>
          <textarea
            id="content"
            value={this.state.content}
            placeholder="内容を入力"
            onChange={e => this.setState({ content: e.target.value })}
            style={{ width: "500px", height: "100px", marginTop: "10px" }}
          />
        </div>
        {this.state.isSending ? (
          "送信中"
        ) : (
          <button onClick={e => this.createPost(e)}>投稿する</button>
        )}
      </form>
    );
  }
}

const CREATE_POST_MUTATION = gql`
  mutation createPostMutation($title: String!, $content: String) {
    createPost(title: $title, content: $content) {
      id
    }
  }
`;

export default graphql(CREATE_POST_MUTATION, {
  name: "createPostMutation"
})(PostForm);

```

入力が確定したら、`投稿する`ボタンをクリックすると`createPost()`が非同期で実行され、送信処理が開始されます。

また、PostListの`props.allPostsQuery`同様に`props.createPostMutation`によって、Props経由で、GraphQL APIにデータを渡しています。

### App.js
最後に、上記までに作成したPostList、PostFormをレンダリングするために、ルートコンポーネントのAppを作っていきます。

```react-graphcool-sample/src/components/App.js
import React, { Component, Fragment } from "react";

import PostList from "./PostList";
import PostForm from "./PostForm";

class App extends Component {
  render() {
    return (
      <Fragment>
        <section>
          <h2>投稿一覧</h2>
          <PostList />
        </section>
        <section>
          <h2>新規投稿</h2>
          <PostForm />
        </section>
      </Fragment>
    );
  }
}

export default App;

```

## 完成品
ここまでのソースは下記のGitHubにあげてあります。

https://github.com/galoi/react-graphcool-sample

フォームに入力後、`投稿する`ボタンをクリックすると入力内容がGraphcoolのサービスに保存されますが、保存直後において、上記の実装ではページに自動反映はされないので、保存された記事を確認したい場合は、ブラウザの更新ボタンを押してページを更新する必要があります。

更新ボタンを押さずに、GraphQLのSubscriptionsを用いて自動反映される仕組みについては、下記の続編をご参照ください。

## 続編（Subscriptions）
[Graphcool、React、Apolloを使って、GraphQLのSubscriptionsを実装する](https://qiita.com/yasuhiro-yamada/items/3bcb36504fb5aaa0416c)