GraphQL 概論

WebDINO Japan シニアエンジニア
渡邉浩平

State of JS 2020

人気の技術

データ層分野

はじめに

内容

  • GraphQL とは
  • なぜ GraphQL を使うのか
  • GraphQL Query ハンズオン

GraphQL の基礎を学び、実際に GraphQL API からデータを取得してみる

GraphQL とは

https://graphql.org

GraphQL とは API の問い合わせ言語

クライアントからサーバーへの問い合わせ (GraphQL Query)

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

結果は JSON で返却

{
  "data": {
    "pokemon": {
      "classification": "Mouse Pokémon"
    }
  }
}

https://graphql-pokemon2.vercel.app

歴史

  • 2012 年 Facebook (現 Meta) による開発
  • 2015 年 オープンソース化
  • 2019 年 GraphQL Foundation に移管

Facebook が GraphQL を開発した理由は、モバイルネイティブアプリ対応のため
スマートフォン普及に伴うプラットフォームの多様化が背景

現在はオープンソースな仕様になっており、自由に貢献できる
https://github.com/graphql/graphql-spec

仕様

https://spec.graphql.org

問い合わせ言語 - GraphQL Query

クライアントからサーバーに問い合わせるための言語
https://graphql.org/learn/queries/

GraphQL 以外の身近な問い合わせ言語の例: SQL

スキーマ言語 - GraphQL Schema

データ構造と操作を宣言するための言語
https://graphql.org/learn/schema/

何でないか

  • グラフデータベースではない
  • JavaScript ではない

なぜ GraphQL を使うのか

  1. 単一リクエスト
  2. 型システム
  3. 便利なツール

1. 単一リクエスト

GraphQL は単一のリクエストで指定したデータを取得できる

REST

REST
https://hasura.io/learn/graphql/intro-graphql/graphql-vs-rest/

GraphQL

GraphQL
https://hasura.io/learn/graphql/intro-graphql/graphql-vs-rest/

GraphQL の特徴

特徴 REST GraphQL
オーバーフェッチの解消 ❌ ✅
アンダーフェッチの解消 ❌ ✅
エンドポイントの管理の容易さ ❌ ✅
エンドポイントの実装の単純さ ✅ ❌

単一の HTTP POST (読み取りのみなら GET) リクエストで複数リソースを操作できる
その代わり、エンドポイントの実装は REST での実装より複雑な傾向がある

クライアント・ドリブン

従来の REST はどちらかというとサーバー側の都合
シンプルなデータ構造が役に立ってきた

現代のモバイルアプリケーションの開発はクライアントが中心の世界
データの問い合わせのより強力な表現が重要になってきた

"GraphQL: Client-Driven Development" @beyang (2017)
https://about.sourcegraph.com/graphql/graphql-client-driven-development/

2. 型システム

単一リクエストを支える GraphQL エンドポイントの構築と正確なデータ構造の維持
クライアントアプリケーションを実行するプラットフォームの多様化が背景

GraphQL Schema

データ構造と操作を宣言するスキーマ言語

"""ポケモンを表します"""
type Pokemon {
  """このオブジェクトのID"""
  id: ID!

  """このポケモンの名前"""
  name: String

  # ...
}

"""ポケモンの寸法を表します"""
type PokemonDimension {
  # ...
}

オブジェクトの種類とその構造を宣言できる

特定のプログラミング言語に依存しない

// JavaScript
const pokemonQuery = `{ pokemon(name: "Pikachu") { classification } }`;

fetch(`http://example/?${new URLSearchParams({ query: pokemonQuery })}`)
  .then((r) => r.json())
  .then(({ data }) => console.log(data?.pokemon?.classification));
// => "Mouse Pokémon"
// Kotlin
val r = apolloClient.query(pokemonQuery).await()
Log.d(r?.data?.pokemon?.classification)
// Swift
apollo.fetch(query: pokemonQuery) { result in
  guard let data = try? result.get().data else { return }
  print(data.pokemon?.classification)
}

3. 便利なツール

クライアントアプリケーションの設計変更に対応するためのツールが提供されている

便利なツールの紹介

  • GraphiQL … GraphQL の開発環境
  • Public GraphQL APIs … 公開されている GraphQL API の一覧
  • GraphQL Code Generator … 自動コード生成
  • Hasura … GraphQL サーバー

GraphiQL

GraphiQL

Public GraphQL APIs

公開されている GraphQL API 一覧の紹介
GraphQL がどういうものか実際に試してみるのに便利

たとえば

https://apis.guru/graphql-apis/

GraphQL Code Generator

コードの生成

$ graphql-codegen

使う

import { usePokemonQuery } from "./generated";

export default () => {
  const { data } = usePokemonQuery();
  return data?.pokemon?.classification;
};

React, Vue, Kotlin, etc.

https://www.graphql-code-generator.com

Hasura

GraphQL サーバー
接続したデータベースを自動的に GraphQL API として提供

https://hasura.io

ここまでのまとめ

  • GraphQL とは API の問い合わせ言語
  • 特徴
    • 単一リクエスト … クライアントの無駄なやり取りを減らせる
    • 型システム … プラットフォームを問わない
    • 便利なツール … 設計変更しやすい

GraphQL Query ハンズオン

GraphQL Query ハンズオン

内容

  • GraphQL の操作
  • query 操作によるデータの取得

実際に GraphQL API からデータを取得してみる

GraphQL Operation

3 種類の操作

  • query - 読み取り
  • mutation - 書き込み
  • subscription - イベントストリーム

1 つのリクエストに複数の操作を含めることができる

query 操作によるデータの取得

ゲットだぜ!

実際に Pokémon API (非公式) を使ってデータを取得してみる

https://graphql-pokemon2.vercel.app/?query=query%20%7B%0A%20%20pokemons(first%3A%20151)%20%7B%0A%20%20%20%20name%0A%20%20%7D%0A%7D

この URL にアクセス

または

  1. https://graphql-pokemon2.vercel.app にアクセス
  2. 下記の Query を入力 > 実行 (▶) を選択
query {
  pokemons(first: 151) {
    name
  }
}

取得結果

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

ポケモンに関する情報を JSON で取得できた

基本的な構文

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

query … 操作
pokemons, name … フィールド
first… 引数
151 … 値

別の取得例

https://graphql-pokemon2.vercel.app/?query=query%20%7B%0A%20%20pokemon(name%3A%20%22Pikachu%22)%20%7B%0A%20%20%20%20classification%0A%20%20%7D%0A%7D

この URL にアクセス

または

  1. https://graphql-pokemon2.vercel.app にアクセス
  2. 下記の Query を入力 > 実行 (▶) を選択
query {
  pokemon(name: "Pikachu") {
    classification
  }
}

取得結果

{
  "data": {
    "pokemon": {
      "classification": "Mouse Pokémon"
    }
  }
}

ポケモンに関する情報を JSON で取得できた

基本的な構文

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

query … 操作
pokemon, classification … フィールド
name … 引数
"Pikachu" … 値

子孫関係

フィールドにフィールドを追加することで子孫関係を取得できる

子孫関係の例

query {
  pokemon(name: "Pikachu") {
    classification
    height {
      minimum # <=
      maximum # <= これらのフィールド
    }
  }
}

height フィールドの中の minimum, maximum フィールド

Try it!

Pokémon GraphQL API
https://graphql-pokemon2.vercel.app

ヒント

  1. { ... }
  2. pokemons(first: 151) { ... } または pokemon(name: "Pikachu") { ... }
  3. Control-Space (or Shift-Space)
  4. フィールドをクリック

発展的な構文

  • 変数 … Query を再利用できる
  • 操作名 … 複数の操作を識別できる
  • エイリアス … フィールドに名前を付ける
  • フラグメント … いくつかのフィールドをまとめる
  • ディレクティブ … Query を修飾できる

変数

変数を使うことで Query を再利用できる

変数の使用例

query ($name: String!) { # <= 変数の定義
  pokemon(name: $name) { # <= 変数の使用
    classification
    height {
      minimum
      maximum
    }
  }
}

$name … 変数 (例えば { "name": "Pikachu" } によって代入)
String! … 型

操作名

操作に名前を付けることで複数の操作を識別できる

操作名の使用例

query fetchPokemonNames { # <= 操作に名前を付ける
  pokemons(first: 151) {
    name
  }
}

query fetchPikachu {      # <= 操作に名前を付ける
  pokemon(name: "Pikachu") {
    classification
  }
}

fetchPokemonNames, fetchPikachu … 操作名

エイリアス

フィールドに名前を付ける

エイリアスの使用例

query {
  pikachu: pokemon(name: "Pikachu") { # <= フィールドに名前を付ける
    classification
  }
}

取得結果

{
  "data": {
    "pikachu": { // <= 名付けたプロパティで取得できる
      "classification": "Mouse Pokémon"
    }
  }
}

フラグメント

いくつかのフィールドをまとめ、そのフィールドを取得する際に使用できる

フラグメントの使用例

# フラグメントの定義
fragment dimension on PokemonDimension {
  minimum
  maximum
}

query {
  pokemon(name: "Pikachu") {
    classification
    height {
      ...dimension # <= フラグメントの使用
    }
    weight {
      ...dimension # <= フラグメントの使用
    }
  }
}

ディレクティブ

Query を修飾できる

@include ディレクティブの使用例

query ($showClassification: Boolean!) {
  pokemon(name: "Pikachu") {
    classification @include(if: $showClassification)
  }
}

変数

{
  "showClassification": true
}

@include ディレクティブは条件に応じてフィールドを含めるかどうかを決める
これ以外にも、いくつかディレクティブがある

まとめ

  • 基本的な構文
    • 操作
    • フィールド
    • 引数と値
    • 子孫関係
  • 発展的な構文
    • 変数
    • 操作名
    • エイリアス
    • フラグメント
    • ディレクティブ

フィードバック

このスライドを編集する / 問題を報告する

後付

より理解を深めるための知識

セキュリティ

セキュリティの懸念事項は一般的な Web サービスと同様に存在

認証・認可

GraphQL 仕様に含まないので一般的な Web の認証・認可の設計と同様に行う

キャッシュ

GraphQL にはグローバルなオブジェクトの識別子の宣言によるキャッシュ機構がある

もし HTTP GET メソッドを使用する場合、HTTP キャッシュを利用できる

JSON Serialization

GraphQL Value JSON Value
Map Object
List Array
Null null
String/Enum Value String
Boolean true or false
Int/Float Number

https://spec.graphql.org/June2018/#sec-JSON-Serialization

@license https://cdn.jsdelivr.net/npm/highlightjs-graphql@1.0.2/LICENSE