kakakakakku blog

Weekly Tech Blog: Keep on Learning!

GraphQL Pokémon を使って楽しく学ぶ GraphQL クエリ

GraphQL クエリを学ぶ場合,気軽に使える API があると便利だと思う.よく見るのは GitHub GraphQL APIStar Wars API だけど,個人的に Star Wars の映画を観たことがなく,データの理解が難しいため,良さそうな API を探していた.すると,GitHub で公開されている「GraphQL Pokémon」を発見した.GraphQL で「ポケモン(第一世代)」のポケモンを検索することができる.これは楽しそう!さっそく試してみた.

github.com

クエリ環境

GraphQL Pokémon には GraphiQL コンソールがあり,ブラウザから簡単にクエリを実行できる.

個人的には GraphQL Playground の Mac アプリをよく使っている.インストールしておくと良いと思う.

electronjs.org

f:id:kakku22:20191230121746p:plain

GraphQL スキーマ

GraphQL Pokémon の GraphQL スキーマを以下に載せた.要点をまとめると,ポケモンを表現する Pokemon 型を軸とし,ワザを表現する PokemonAttack 型 と Attack 型 / サイズを表現する PokemonDimension 型 / 進化条件を表現する PokemonEvolutionRequirement 型となる.マスタデータとしてはそこそこ揃っている気がする.そして「ポケモン(第一世代)」を対象とするため,ミュウ (No.151) までデータセットに含まれている.

  • Pokemon
    • PokemonAttack
      • Attack
    • PokemonDimension
    • PokemonEvolutionRequirement
type Attack {
  name: String
  type: String
  damage: Int
}

type Pokemon {
  id: ID!
  number: String
  name: String
  weight: PokemonDimension
  height: PokemonDimension
  classification: String
  types: [String]
  resistant: [String]
  attacks: PokemonAttack
  weaknesses: [String]
  fleeRate: Float
  maxCP: Int
  evolutions: [Pokemon]
  evolutionRequirements: PokemonEvolutionRequirement
  maxHP: Int
  image: String
}

type PokemonAttack {
  fast: [Attack]
  special: [Attack]
}

type PokemonDimension {
  minimum: String
  maximum: String
}

type PokemonEvolutionRequirement {
  amount: Int
  name: String
}

type Query {
  query: Query
  pokemons(first: Int!): [Pokemon]
  pokemon(id: String, name: String): Pokemon
}

クエリ :「ピカチュウ」を検索する

まず,1番簡単なクエリを実行する.pokemon クエリに name: "Pikachu" を指定する.取得するフィールドは number (番号)name (英語名)types (タイプ) とした.

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

すると「ピカチュウ」を検索できる.ピカチュウは Electric (でんき) タイプであると確認できる.

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

クエリ :「ピカチュウ」の進化を検索する

evolutions (進化) フィールドを指定すると進化するポケモンを Pokemon 型で取得できる.

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

すると「ピカチュウ」の進化は「ライチュウ」であると確認できる.REST API と異なり,1回のクエリで階層データまで取得できるのは GraphQL のメリットと言える.

{
  "data": {
    "pokemon": {
      "number": "025",
      "name": "Pikachu",
      "types": [
        "Electric"
      ],
      "evolutions": [
        {
          "number": "026",
          "name": "Raichu",
          "types": [
            "Electric"
          ]
        }
      ]
    }
  }
}

クエリ :「ヒトカゲ」の進化を検索する

3段階進化をする「ヒトカゲ」を検索する.英語版だと名前が異なるため,今回は name: "Charmander" を指定する.

query {
  pokemon(name: "Charmander") {
    number
    name
    types
    evolutions {
      number
      name
      types
    }
  }
}

すると「ヒトカゲ (Charmander)」の進化は「リザード (Charmeleon)」「リザードン (Charizard)」であると確認できる.

{
  "data": {
    "pokemon": {
      "number": "004",
      "name": "Charmander",
      "types": [
        "Fire"
      ],
      "evolutions": [
        {
          "number": "005",
          "name": "Charmeleon",
          "types": [
            "Fire"
          ]
        },
        {
          "number": "006",
          "name": "Charizard",
          "types": [
            "Fire",
            "Flying"
          ]
        }
      ]
    }
  }
}

クエリ : フラグメントを使う

進化を検索したときに Pokemon 型のフィールドを2箇所に定義していたため,GraphQL クエリの「フラグメント」を使って共通化する.以下のように Pokemon 型のフラグメント pokemonInfo を定義し,クエリに含めた.さらに今回は取得するフィールドに weaknesses (弱点) を追加した.

fragment pokemonInfo on Pokemon {
  number
  name
  types
  weaknesses
}

query {
  pokemon(name: "Pikachu") {
    ...pokemonInfo
    evolutions {
      ...pokemonInfo
    }
  }
}

「ピカチュウ」「ライチュウ」Ground (じめん) タイプが弱点であると確認できる.フラグメントを使うと GraphQL クエリをシンプルに書けるし,可読性も高くなるため,タイミングを見極めて積極的に使っていく.

{
  "data": {
    "pokemon": {
      "number": "025",
      "name": "Pikachu",
      "types": [
        "Electric"
      ],
      "weaknesses": [
        "Ground"
      ],
      "evolutions": [
        {
          "number": "026",
          "name": "Raichu",
          "types": [
            "Electric"
          ],
          "weaknesses": [
            "Ground"
          ]
        }
      ]
    }
  }
}

クエリ :「ピカチュウ」と「ライチュウ」のワザを検索する

attacks (ワザ) フィールドを指定すると,ポケモンのワザを PokemonAttack 型と Attack 型で取得できる.PokemonAttack 型 では fast (ワザ)special (わざマシン) の2種類のフィールドが定義されている.今回は Pokemon 型に加えて Attack 型も「フラグメント」を使って共通化する.

fragment attackInfo on Attack {
  name
  type
  damage
}

fragment pokemonInfo on Pokemon {
  number
  name
  types
  weaknesses
  attacks {
    fast {
      ...attackInfo
    }
    special {
      ...attackInfo
    }
  }
}

query {
  pokemon(name: "Pikachu") {
    ...pokemonInfo
    evolutions {
      ...pokemonInfo
    }
  }
}

すると「ピカチュウ」「ライチュウ」のワザを取得できる.ワザの英語名を整理すると以下になると思う(間違っている可能性もある).

  • ピカチュウ
    • fast
      • Quick Attack (でんこうせっか)
      • Thunder Shock (でんきショック)
    • special
      • Discharge (ほうでん)
      • Thunder (かみなり)
      • Thunderbolt (10まんボルト)
  • ライチュウ
    • fast
      • Spark (スパーク)
      • Thunder Shock (でんきショック)
    • special
      • Brick Break (かわらわり)
      • Thunder (かみなり)
      • Thunder Punch (かみなりパンチ)
{
  "data": {
    "pokemon": {
      "number": "025",
      "name": "Pikachu",
      "types": [
        "Electric"
      ],
      "weaknesses": [
        "Ground"
      ],
      "attacks": {
        "fast": [
          {
            "name": "Quick Attack",
            "type": "Normal",
            "damage": 10
          },
          {
            "name": "Thunder Shock",
            "type": "Electric",
            "damage": 5
          }
        ],
        "special": [
          {
            "name": "Discharge",
            "type": "Electric",
            "damage": 35
          },
          {
            "name": "Thunder",
            "type": "Electric",
            "damage": 100
          },
          {
            "name": "Thunderbolt",
            "type": "Electric",
            "damage": 55
          }
        ]
      },
      "evolutions": [
        {
          "number": "026",
          "name": "Raichu",
          "types": [
            "Electric"
          ],
          "weaknesses": [
            "Ground"
          ],
          "attacks": {
            "fast": [
              {
                "name": "Spark",
                "type": "Electric",
                "damage": 7
              },
              {
                "name": "Thunder Shock",
                "type": "Electric",
                "damage": 5
              }
            ],
            "special": [
              {
                "name": "Brick Break",
                "type": "Fighting",
                "damage": 30
              },
              {
                "name": "Thunder",
                "type": "Electric",
                "damage": 100
              },
              {
                "name": "Thunder Punch",
                "type": "Electric",
                "damage": 40
              }
            ]
          }
        }
      ]
    }
  }
}

クエリ :「イーブイ」と進化ポケモンの画像を検索する

GraphQL スキーマを見ると Pokemon 型に image フィールドがある.3種類に進化する可能性のある「イーブイ (Eevee)」を検索して,画像を取得する.

fragment pokemonInfo on Pokemon {
  number
  name
  image
}

query {
  pokemon(name: "Eevee") {
    ...pokemonInfo
    evolutions {
      ...pokemonInfo
    }
  }
}

すると image フィールドに画像 URL が含まれていた.

{
  "data": {
    "pokemon": {
      "number": "133",
      "name": "Eevee",
      "image": "https://img.pokemondb.net/artwork/eevee.jpg",
      "evolutions": [
        {
          "number": "134",
          "name": "Vaporeon",
          "image": "https://img.pokemondb.net/artwork/vaporeon.jpg"
        },
        {
          "number": "135",
          "name": "Jolteon",
          "image": "https://img.pokemondb.net/artwork/jolteon.jpg"
        },
        {
          "number": "136",
          "name": "Flareon",
          "image": "https://img.pokemondb.net/artwork/flareon.jpg"
        }
      ]
    }
  }
}

「GraphQL Pokémon」の GitHub を見ると,ポケモン画像を使った React サンプルアプリも公開されていた.GraphQL / React / Relay / Material-UI を組み合わせた実装例としても参考になる.以下にサンプルアプリのキャプチャを載せておく.

f:id:kakku22:20191230130835p:plain

クエリ : ポケモン一覧を検索する

GraphQL スキーマを見ると,もう1種類 pokemons というクエリも用意されている.柔軟に検索できるのかと予想したけど,実際には first パラメータしかなく,最初から何種類のポケモンを取得する?という用途だった.一覧を作る以外は用途はなさそう.

query {
  pokemons(first: 3) {
    name
  }
}

今回は first: 3 を指定したため,「フシギダネ(Bulbasaur)」「フシギソウ (Ivysaur)」「フシギバナ (Venusaur)」を取得できる.ポケモンの英語名を覚える練習に使えそう.英語名だと本当にわからなかった.

{
  "data": {
    "pokemons": [
      {
        "name": "Bulbasaur"
      },
      {
        "name": "Ivysaur"
      },
      {
        "name": "Venusaur"
      }
    ]
  }
}

まとめ

  • GraphQL クエリを学ぶ場合,気軽に使える API があると便利
  • GitHub GraphQL APIStar Wars API 以外にもたくさんある
  • 「GraphQL Pokémon」を使うと GraphQL で「ポケモン(第一世代)」のポケモンを検索することができて楽しく学べる!