GraphQL の話題を聞くことはあったのですが実際に触ったことがなく、使い方やメリットを知るために公式サイトの React と GraphQL クライアントライブラリ Apollo を使ったチュートリアル をやってみました。
この記事ではチュートリアルでやったことや、やりながら調べたことなどを書き残しています。
事前に公式サイトの How To GraphPL を読んで GraphQL の概要は理解しておきました。動画とテキストの両方を公開しており、丁寧に書かれていて大変わかりやすかったです。GraphQL を初めて触る人はまず読んでみたほうが良いかと思います。
*GraphQLとは
Facebook が OSS として開発している Web API のための問い合わせ(クエリ)言語です。現在広く使われている RESTful Web API の代替として、クライアントがサーバから柔軟にデータ取得や変更をするために開発されました。GitHub や Atlassian、Netflix などの企業がこの GraphQL を導入しています。REST はリソースごとに API を分けるためリクエスト量が多くなるといった問題点がありますが、GraphQL は単一のエンドポイントのみを公開してクライアントが要求したデータのみ取得することができます。クライアントが必要な情報を定義するだけなので、UI が変更されてもバックエンドを修正する必要がなくなります。また、REST だと API ドキュメントと実装に乖離ができてしまいますが、GraphQL はドキュメントを自動生成してくれるため開発工数を削減することができます。
リクエストされたクエリはスキーマ言語(SDL)で記述したスキーマに従って実行され、JSON形式のレスポンスを生成します。JSON形式であるため、画像や動画などの大容量バイナリの扱いが難しいといったデメリットもあります。
<特徴まとめ>
- 複数の API リクエストを1つにまとめられる
- 複数サービスの API をまとめる API Gateway として使える
- 共通したバックエンドを効率的に開発できる
- クライアントとサーバー間のインターフェースがクリーンになる
- API ドキュメントの作成に費やす時間を削減する(自動生成)
- 型システムを定義することで意図しないデータの通信を防ぐ
- クライアントからレスポンス形式を指定できる
- subscriptionを使ったイベントドリブンなリアルタイム更新を行うことができる
- データ構造をモック化することで簡単にテストを行うことができる
*Apollo とは
GraphQL を簡単に操作するための OSS ライブラリです。- Apollo Client
 React や Vue.js、ネイティブといったクライアントをサポート
- Apollo Server
 バックエンドからデータを取得する仕組みを構築(Node.js でGraphQL API を構築)
- Apollo Engine
 GraphQL の実行状況を監視したり、エラートラッキング、キャッシュの利用状況の監視などを行うことができるクラウドのサービス
*Prisma とは
データベースを GraphQL API に変える OSS ツールです。*参考
- How To GraphPL
- React + Apollo tutotial
- Apollo /公式サイト
- apollo-client /GitHub
- Prisma /公式サイト
- 「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ
*環境
- MacOS
- node 12.4.0
- create-react-app 3.1.0
- react 16.9.0
- graphql 14.4.2
- react-apollo 3.0.0
- apollo-boost 0.4.3
- prisma 1.34.5
*Reactプロジェクトを作成
React のプロジェクトを作成します。今回は、任意の作業ディレクトリ配下で
react-appという名前の React プロジェクトを作成しました。## インストール
$ sudo npm install -g create-react-app
## プロジェクト作成
$ create-react-app react-app
アプリケーションを起動すると、React のサンプル画面が表示されます。
$ cd react-app/
$ npm start
プロジェクトの構造を修正します。
srcディレクトリ配下にstylesとcomponentsディレクトリを作成し、stylesにindex.css, App.css、componentsにApp.jsを移動します。このファイル移動に伴い、
index.jsとApp.jsの参照を修正します。<react-app/src/index.js>
import React from "react";
import ReactDOM from "react-dom";
// ---- ↓修正 ----
import "./styles/index.css";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
...
<react-app/src/components/App.js>
import React, { Component } from "react";
import logo from "../logo.svg";
// ---- ↓修正 ----
import "../styles/App.css";
...
修正後のディレクトリ構成は下記になります。
react-app
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src
 ├── App.test.js
 ├── components
 │   └── App.js
 ├── index.js
 ├── logo.svg
 ├── serviceWorker.js
 └── styles
     ├── App.css
     └── index.css
*スタイルの修正
今回はチュートリアルに従ってTachyonsというCSSライブラリを使うので、index.htmlにlinkを追加します。<react-app/public/index.html>
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!---- ↓追加 ---->
    <link
      rel="stylesheet"
      href="https://unpkg.com/tachyons@4.2.1/css/tachyons.min.css"
    />
...
index.cssを下記に修正します。<react-app/src/styles/index.css>
body {
  margin: 0;
  padding: 0;
  font-family: Verdana, Geneva, sans-serif;
}
input {
  max-width: 500px;
}
.gray {
  color: #828282;
}
.orange {
  background-color: #ff6600;
}
.background-gray {
  background-color: rgb(246,246,239);
}
.f11 {
  font-size: 11px;
}
.w85 {
  width: 85%;
}
.button {
  font-family: monospace;
  font-size: 10pt;
  color: black;
  background-color: buttonface;
  text-align: center;
  padding: 2px 6px 3px;
  border-width: 2px;
  border-style: outset;
  border-color: buttonface;
  cursor: pointer;
  max-width: 250px;
}
*Apolloのインストール
下記コマンドを実行して Apollo をインストールします。- apollo-boost----- セットアップに必要なメモリキャッシュやエラー処理、状態管理などをまとめたパッケージです。
- react-apollo----- React コンポーネントから GraphQL サーバにアクセスするためのパッケージです。
- graphql----- GraphQL のパーサーです。
$ npm install apollo-boost react-apollo graphql --save
index.jsに下記を追加します。ApolloProviderコンポーネントでその他のコンポーネントを囲うことで、子コンポーネントから GraphQL にアクセスできるようになります。<react-app/src/index.js>
// apolloパッケージからインポート
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
// GraphQL APIに接続
const httpLink = createHttpLink({
  uri: "http://localhost:4000"
});
// ApolloClientのインスタンスを作成
const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
});
// Reactアプリのルートコンポーネントをレンダリング
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);
*バックエンドの実装(ダウンロード)
バックエンドは実装済みのプロジェクトをダウンロードして使います。React プロジェクト配下で下記コマンドを実行するとserverディレクトリが作成されます。$ curl https://codeload.github.com/howtographql/react-apollo/tar.gz/starter | tar -xz --strip=1 react-apollo-starter/server
serverディレクトリの中は下記になっています。schema.graphqlでスキーマを定義しています。server
├── node_modules
├── package-lock.json
├── package.json
├── prisma
├── yarn.lock
└── src
 ├── generated
 │   └── prisma-client
 │       ├── index.d.ts
 │       ├── index.js
 │       └── prisma-schema.js
 ├── index.js
 ├── resolvers
 │   ├── Link.js
 │   ├── Mutation.js
 │   ├── Query.js
 │   ├── Subscription.js
 │   ├── User.js
 │   └── Vote.js
 ├── schema.graphql
 └── utils.js
schema.graphqlのスキーマ言語に従ってレスポンスが生成されます。オブジェクト毎ににフィールドを定義します。フィールドには型を定義することができます。- Type----------- 言語でいうクラスのような使い方をする複数のフィールドからなる型
- !--------------- 必須項目
- Field---------- 名前 : 型
- Interface----- 複数のTypeに共通のフィールドを持たせられる
- Union----------- 定義された型のうちどちらかを示す抽象型
- Scalar---------- ただ1つの値からなる型
- Enum------------- 特定の値をもつ型(- Scalarの一種)
scalar DateTime
"---- queryのルートとなる定義 ----"
type Query {
  info: String!
  feed(filter: String, skip: Int, first: Int, orderBy: LinkOrderByInput): Feed!
}
enum LinkOrderByInput {
  description_ASC
  description_DESC
  url_ASC
  url_DESC
  createdAt_ASC
  createdAt_DESC
}
type Feed {
  links: [Link!]!
  count: Int!
}
type Mutation {
  post(url: String!, description: String!): Link!
  signup(email: String!, password: String!, name: String!): AuthPayload
  login(email: String!, password: String!): AuthPayload
  vote(linkId: ID!): Vote!
}
type Subscription {
  newLink: Link
  newVote: Vote
}
type AuthPayload {
  token: String
  user: User
}
type User {
  id: ID!
  name: String!
  email: String!
  links: [Link!]!
}
type Link {
  id: ID!
  createdAt: DateTime!
  description: String!
  url: String!
  postedBy: User
  votes: [Vote!]!
}
type Vote {
  id: ID!
  link: Link!
  user: User!
}
*デモサーバーの起動
prismaをインストールしデプロイします。ターミナルで下記コマンドを実行します。使うサーバーを聞かれるので
Demo server + MySQL databaseを選択します。$ cd server/
$ npm install
$ sudo npm install -g prisma
$ prisma deploy
? Set up a new Prisma server or deploy to an existing server? Demo server + MySQL database
GitHub の認証を行います。
認証後にリージョンなどを聞かれるので適宜答えます。
? Choose the region of your demo server XXXX/demo-eu1
? Choose a name for your service server
? Choose a name for your stage dev
serverディレクトリで下記コマンドを実行し、サーバーを起動します。$ npm start
下記にアクセスすると GraphQL Playground が表示されます。
http://localhost:4000
GraphQL はクエリ言語を使って API のリクエストを行います。書き方は主に3パターンあります。
- query-------------- データ取得
- mutation---------- データ更新/登録
- subscription----- サーバーサイドからのイベント通知
mutationとして POST する処理を定義しています。mutation CreatePrismaLink {
  post(
    description: "Prisma turns your database into a GraphQL API",
    url: "https://www.prismagraphql.com"   
  ) {
    id
  }
}
mutation CreateApolloLink {
  post(
    description: "The best GraphQL client for React",
    url: "https://www.apollographql.com/docs/react/"
  ) {
    id
  }
}
下記を実行すると、登録したデータを参照することができます。
query GetLink{
  feed {
    links {
      id
      description
      url
    }
  }
}
JSON形式で実行結果が得られます。
{
  "data": {
    "feed": {
      "links": [
        {
          "id": "cjz5jo3sc8vgb0b53tox8j41q",
          "description": "The best GraphQL client for React",
          "url": "https://www.apollographql.com/docs/react/"
        },
        {
          "id": "cjz6v5rziank70b53olfiymr3",
          "description": "Prisma turns your database into a GraphQL API",
          "url": "https://www.prismagraphql.com"
        },
        {
          "id": "cjz6v5xrdanpz0b53dc5k2omf",
          "description": "Prisma turns your database into a GraphQL API",
          "url": "https://www.prismagraphql.com"
        }
      ]
    }
  }
}
*モックデータの表示
React アプリでデータを表示できるようにします。まずはモックデータで表示し、そのあと GraphQL を使って表示させます。
Link.jsのコンポーネントを新規作成し、propsで受け取ったデータを表示するようにします。<react-app/src/components/Link.js>
import React, { Component } from "react";
class Link extends Component {
  render() {
    return (
      <div>
        <div>
          {this.props.link.description} ({this.props.link.url})
        </div>
      </div>
    );
  }
}
export default Link;
LinkList.jsのコンポーネントを新規作成し、モックデータの定義とLinkコンポーネントの呼び出しをします。<react-app/src/components/LinkList.js>
import React, { Component } from "react";
import Link from "./Link";
class LinkList extends Component {
  render() {
    const linksToRender = [
      {
        id: "1",
        description: "Prisma turns your database into a GraphQL API!",
        url: "https://www.prismagraphql.com"
      },
      {
        id: "2",
        description: "The best GraphQL client",
        url: "https://www.apollographql/docs/react/"
      }
    ];
    return (
      <div>
        {linksToRender.map(link => (
          <Link key={link.id} link={link} />
        ))}
      </div>
    );
  }
}
export default LinkList;
App.jsからLinkListコンポーネントを呼び出すよう修正します。<react-app/src/components/App.js>
import React, { Component } from "react";
// ---- ↓追加 ----
import LinkList from "./LinkList";
class App extends Component {
  render() {
    // ---- ↓修正 ----
    return <LinkList />;
  }
}
export default App;
アプリケーションを起動するとモックデータが表示されます。
$ npm start
*GraphQLクエリを使ったデータ表示
LinkList.jsを下記に修正します。react-apolloのQueryコンポーネントを使ってクエリの結果を取得できるようにします。取得した結果はdataに入ります。<react-app/src/components/LinkList.js>
import React, { Component } from "react";
import Link from "./Link";
// ---- ↓インポートを追加 ----
import { Query } from "react-apollo";
import gql from "graphql-tag";
// ---- gqlでGraphQLの文字列を解析 ----
const FEED_QUERY = gql`
  {
    feed {
      links {
        id
        createdAt
        url
        description
      }
    }
  }
`;
class LinkList extends Component {
  render() {
    return (
      // ---- Queryコンポーネントで結果を取得 ----
      <Query query={FEED_QUERY}>
        {({ loading, error, data }) => {
          // ---- 処理が進行中 -----
          if (loading) return <div>Fetching</div>;
          // ---- リクエストが失敗 -----
          if (error) return <div>Error</div>;
          const linksToRender = data.feed.links;
          return (
            <div>
              {linksToRender.map(link => (
                <Link key={link.id} link={link} />
              ))}
            </div>
          );
        }}
      </Query>
    );
  }
}
export default LinkList;
サーバー用と React アプリ用のサーバ2種類を起動する必要があります。
serverディレクトリ配下で下記コマンドを実行して、サーバーを起動します。/react-app/server
$ npm start
クライアント側のサーバーを起動します。
/react-app/
$ npm start
下記にアクセスすると、Playground で登録したデータが表示されます。
http://localhost:3000/
*所感
実装方法や書き方を覚える必要はありますが、型の定義など直感的で覚えやすく、他の言語にも多く対応しているので使えるケースは多そうだと感じました。(Go や Java、PHP、Python、Scala、Ruby など)サーバーサイドの実装を気にせずにクライアント側で欲しいデータだけ要求できるといった点が特に魅力的でした。昔はエンドポイントが1つだったことで、アクセス解析ができないといったデメリットもあったそうですが、現在は
Apollo Engineというサービスで解決できるようになったようです。現在も活発にOSS開発がされているようなので、今後ますます使いやすくなるのではと期待しています。







