はじめてのGraphQL

GraphQLとは

GraphQLとは、Facebookが開発したAPI向けのクエリ言語です。RESTに変わるサーバーとクライアントの通信手段として注目を集めています。 リクエスト・レスポンスの型の定義ができる、フロント側から取得するデータを選択することができるなどの特徴があります。

GraphQL自体はSQLのようなクエリ言語としての位置づけなので、Java、Node.js、Ruby、JavaScript、Pythonなど様々な言語で利用することができます。

最近では、NetFlixがRESTをやめてGraphQLに乗り換えた事例が記憶に新しいです。

なぜRESTではなくGraphQLを使うのか

GraphQLは、当時RESTfulなAPIサーバーを利用していたFacebookがクライアント/サーバーアプリケーションの性能上の課題とデータ構造の要件を満たす解決策として開発されました。

RESTにどのような課題があって、それをGraphQLはどのように解決するのかという視点で見ていきます。

RESTの課題

大きすぎるレスポンス

RESTの課題の一つは、レスポンスが大きすぎることです。

例えば、PokeAPIからピカチュウのデータを取得するGETリクエストを発行すると次のようなレスポンスが返されます。

{"abilities":[{"ability":{"name":"static","url":"https://pokeapi.co/api/v2/ability/9/"},"is_hidden":false,"slot":1},{"ability":{"name":"lightning-rod","url":"https://pokeapi.co/api/v2/ability/31/"},"is_hidden":true,"slot":3}],"base_experience":112,"forms":[{"name":"pikachu","url":"https://pokeapi.co/api/v2/pokemon-form/25/"}],"game_indices":[{"game_index":84,"version":{"name":"red","url":"https://pokeapi.co/api/v2/version/1/"}},{"game_index":84,"version":{"name":"blue","url":"https://pokeapi.co/api/v2/version/2/"}},{"game_index":84,"version":{"name":"yellow","url":"https://pokeapi.co/api/v2/version/3/"}},{"game_index":25,"version":{"name":"gold","url":"https://pokeapi.co/api/v2/version/4/"}},{"game_index":25,"version":{"name":"silver","url":"https://pokeapi.co/api/v2/version/5/"}},{"game_index":25,"version":{"name":"crystal","url":"https://pokeapi.co/api/v2/version/6/"}},{"game_index":25,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"game_index":25,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"game_index":25,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"game_index":25,"version":{"name":"firered","url":"https://pokeapi.co/api/v2/version/10/"}},{"game_index":25,"version":{"name":"leafgreen","url":"https://pokeapi.co/api/v2/version/11/"}},{"game_index":25,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"game_index":25,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"game_index":25,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"game_index":25,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"game_index":25,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"game_index":25,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"game_index":25,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"game_index":25,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"game_index":25,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}}],"height":4,"held_items":[{"item":{"name":"oran-berry","url":"https://pokeapi.co/api/v2/item/132/"},"version_details":[{"rarity":50,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"rarity":50,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"rarity":50,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"rarity":50,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":50,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":50,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":50,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":50,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":50,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":50,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}}]},{"item":{"name":"light-ball","url":"https://pokeapi.co/api/v2/item/213/"},"version_details":[{"rarity":5,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"rarity":5,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"rarity":5,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"rarity":5,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":5,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":5,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":5,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":5,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":1,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":1,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"rarity":5,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"rarity":5,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}},{"rarity":5,"version":{"name":"x","url":"https://pokeapi.co/api/v2/version/23/"}},{"rarity":5,"version":{"name":"y","url":"https://pokeapi.co/api/v2/version/24/"}},{"rarity":5,"version":{"name":"omega-ruby","url":"https://pokeapi.co/api/v2/version/25/"}},{"rarity":5,"version":{"name":"alpha-sapphire","url":"https://pokeapi.co/api/v2/version/26/"}},{"rarity":5,"version":{"name":"sun","url":"https://pokeapi.co/api/v2/version/27/"}},{"rarity":5,"version":{"name":"moon","url":"https://pokeapi.co/api/v2/version/28/"}},{"rarity":5,"version":{"name":"ultra-sun","url":"https://pokeapi.co/api/v2/version/29/"}},{"rarity":5,"version":{"name":"ultra-moon","url":"https://pokeapi.co/api/v2/version/30/"}}]}],"id":25,"}
# 省略

これは実際のレスポンスの本の一部であり、実際にはもっと巨大です。本体クライアントが必要なデータは名前、タイプだけだったかもしれませんが、大量の必要のないデータを受け取る必要があります。

{
  "name": "pikachu",
  "types": ["electric"]
}

少なすぎるレスポンス

今度は逆にレスポンスが少なすぎるという問題が起こる可能性があります。 例えば、ピカチュウがどのような進化をするのかデータを取得したくなるかもしれません。

PokeAPIで進化に関する情報を取得するにはまずhttps://pokeapi.co/api/v2/pokemon/pikachuのデータを取得した上で、speciesに関する情報を取得します。

 "species": {
    "name": "pikachu",
    "url": "https://pokeapi.co/api/v2/pokemon-species/25/"
  },

取得したspeciesの情報からさらにAPIリクエストを送る必要があります。evolution_chainという情報からピカチュウの進化情報が取得できるので、さらにAPIリクエストを送ります。

 "evolution_chain": {
    "url": "https://pokeapi.co/api/v2/evolution-chain/10/"
  },

ようやくここでピカチュウはピチューから進化してさらにライチュウに進化するという情報が得られました。ピチューとライチュウの詳細情報を得るには、さらに2回APIリクエストを送る必要があるでしょう。

"is_baby": false,
 "species": {
   "name": "raichu",
   "url": "https://pokeapi.co/api/v2/pokemon-species/26/"
},
"is_baby": true,
"species": {
  "name": "pichu",
  "url": "https://pokeapi.co/api/v2/pokemon-species/172/"
}

このように1回で十分な情報が取得できないために、いくつもの余分なリクエストを送信しなければなりません。また、それぞれの実際のレスポンスには当然不必要な情報もたくさん含まれています。

エンドポイント、ドキュメントの管理

最後の問題点として、エンドポイント、ドキュメントの管理の煩雑さが挙げられます。REST APIはクライアントに変更が加わるたびに新しいエンドポイントを作成する必要があり、エンドポイントの数が膨れ上がってきます。

また、適切にドキュメントが更新されていればよいですが、エンドポイントの数が多くなるに釣れスキーマなどを管理・保守することは大変になってきます。

GraphQLはどのように解決するのか

これらのREST APIの問題点について、GraphQLがどのように解決するのかを見ていきましょう。 Poke APIの代わりにGraphQL Pokemonを使っていきます。

クエリの発行は、プレイグラウンドからブラウザで試すことができます。

スクリーンショット 20210117 22.03.46.png

ピカチュウのタイプを取得

それでは、REST APIの例と同様にピカチュウの名前とタイプを取得してみましょう。 GraphQLには、QueryMutationSubscriptionの3つのクエリが存在します。GETリクエストに相当するものがQueryです。

Queryを使ってデータを取得しましょう。

query {
  pokemon(name: "Pikachu") {
    name
    types
  }
}

queryコマンドによって、pokemonというスキーマのデータを取得しようとしています。(name: "Pikachu")によって、特定のデータを取得するように指示します。 さらに、取得するフィールドを宣言しています。ここでは、name(名前)とtypes(タイプ)フィールドを宣言しています。これはSQLのSELECT文とよく似ており、宣言したフィールド以外はレスポンスに含まれません。

レスポンスは次のようになります。

{
  "data": {
    "pokemon": {
      "name": "Pikachu",
      "types": [
        "Electric"
      ]
    }
  }
}

ピカチュウの進化情報を取得

取得する情報にevolutionsを含めると、進化情報をpokemon型で取得できます。

{
  pokemon(name: "Pikachu") {
    name
    types
    weight {
      minimum
      maximum
    }
    height {
      minimum
      maximum
    }
    image
    evolutions {
      name
      types
      weight {
        minimum
        maximum
      }
      height {
        minimum
        maximum
      }
      image
    }
  }
}

レスポンスは次のとおりです。(Poke APIと異なりGraphQL Pokemonは第一世代の情報しか含まれていないのでレスポンスにピチューは含まれていません・・・)

一度のリクエストだけで、必要なすべての情報が取得できていることがわかります。

{
  "data": {
    "pokemon": {
      "name": "Pikachu",
      "types": [
        "Electric"
      ],
      "weight": {
        "minimum": "5.25kg",
        "maximum": "6.75kg"
      },
      "height": {
        "minimum": "0.35m",
        "maximum": "0.45m"
      },
      "image": "https://img.pokemondb.net/artwork/pikachu.jpg",
      "evolutions": [
        {
          "name": "Raichu",
          "types": [
            "Electric"
          ],
          "weight": {
            "minimum": "26.25kg",
            "maximum": "33.75kg"
          },
          "height": {
            "minimum": "0.7m",
            "maximum": "0.9m"
          },
          "image": "https://img.pokemondb.net/artwork/raichu.jpg"
        }
      ]
    }
  }
}

エンドポイントとドキュメントの管理

GraphQLには、常に1つのエンドポイントしか存在しません。つまり、エンドポイントの管理の問題は考える必要はありません。

また、GraphQLのドキュメントはスキーマに応じて自動で作成されます。プレイグラウンドの右上にあるDocをクリックしてみてください。

スクリーンショット 20210117 22.17.48.png

この記事をシェアする
Hatena

関連記事