LoginSignup
229
170

More than 5 years have passed since last update.

GraphQLって何?

Last updated at Posted at 2017-06-27

まだクエリとミューテーションしか書けていないので他は参考文献よりどうぞ
(時間かかるから徐々に追記します)
(下書きでもいいけどモチベ下がりそう)

はじめに

この記事はできるだけ簡潔に記述することを目的にしています。
詳しいことを知りたい方は他をどうぞ

この記事内では見やすくなるように多少データを整形しています

GraphQLとは

GraphQLはAPI向けの言語です。データの形式のみの定義のため, 言語やデータを保存する方法は依存しません。(要するにDBでもテキストでもいい)
GraphQLの定義に従ってクエリを書き, サーバーと通信を取ることでJSONになって戻ってきます。

クエリとミューテーション

GraphQLには様々なデータの取得や編集に関するものがあります。

Fields

最も簡単なのはこのFieldです。

request
{
  hero {
    name
  }
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

このようにリクエストしたクエリと同じ形でJsonが帰ってきます。
これがGraphQLでは重要です。(すぐに欲しいデータが受け取れる)

Stringやintといった単一のものだけでなくObjectも受け取れます。
また, コメントは先頭に#をつけて表します。

request
{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

上の例ではfriendsは配列のオブジェクトを返します。

Arguments

上のFieldだけでは決まったデータを受け取ることしか出来ません。
そこでArgumentsを追加することでいろいろなデータを柔軟に指定, 取得できます。

request
{
  human(id: "1000") {
    name
    height
  }
}
response
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

これまでAPIデザインとして一般的だったRESTはURLクエリなどでしかArgumentsを送れませんでした。
が、GraphQLではそれぞれのFieldなどに対してもArgumentsをつけることが出来ます。

request
{
  human(id: "1000") {
    name
    height(unit: cm)
  }
}
request
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 172
    }
  }
}

この引数には様々な型があります。また自分で宣言することも可能です。
(型についてはこの先のスキーマとタイプを参照してください)

Aliases

例えばhero(episode: EMPIRE)hero(episode: JEDI)を同じクエリで参照しようとした場合, 同じheroフィールドになってしまい参照できません。そこでエイリアスを利用します。

request
{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
response
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

エイリアスを使うことで両方の結果を受け取れます。

Fragments

同じデータを受け取るのに同じものを何回も宣言するのは冗長です

ex
{
  leftComparison: hero(episode: EMPIRE) {
    name
    appearsIn
    friends {
      name
    }
  }
  rightComparison: hero(episode: JEDI) {
    name
    appearsIn
    friends {
      name
    }
  }
}

フラグメンツを使うことで上の例が下のように簡単に宣言できます

request
{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
response
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Han Solo" },
        { "name": "Leia Organa" },
        { "name": "C-3PO" },
        { "name": "R2-D2" }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

同じ形式の多くのデータを取得する際に便利に使いやすいです。
複数のフラグメントを使うことも可能です。

Variables

ほとんどのアプリではArgumentsに指定するものは変化します。
GraphQLでは内部で固有の形式に変換するため変化するArgumentsを直接書くことはすすめられていません。
なので変数を使いましょう。

まずは変数を使うクエリを$変数名のように宣言します。(1行目)
その変数を使うクエリを今まで通り記述します(2行目)

request
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

変数名: 値のように記述し,変数辞書を作成します。(Jsonのことが多い)

veriablesJson
{
  "episode": "JEDI"
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

クライアント側では新しくクエリを作る必要はなく, ただ単に変数(Json)を渡すだけです。
変数を使わず, クエリを文字列結合で作ることはするべきではありません。

変数定義について

変数定義は($episode: Episode)のように宣言され, $を先頭に変数名, その後ろに型が宣言されます。関数宣言に似ています。

上の例では$episodeは必須ではありません。
必須にするには($episode: Episode!)のように宣言します。
(詳しくはこの先のスキーマとタイプを参照してください)

デフォルトの値を宣言することも可能です。
($episode: Episode = JEDI)のように宣言します。
もちろん変数(Json)を渡された場合にはJsonを優先します。

Operation name

上の例ではquery HeroNameAndFriends { ... }のように書いていますが今まで通りこれらを省略し{ ... }のように記述することも出来ます。

これらを書くことに寄ってサーバーとクライアント側で要求を識別することが簡単になります。

Directives

Variablesを使うことでクエリへの引数を変更できるようにナリましたが、引数だけでなく変数によって取得するデータを変更しなければならないかもしれません。

request
query Hero($episode: Episode, $withAppearsIn: Boolean!, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    appearsIn @skip(if: $withAppearsIn)
    friends @include(if: $withFriends) {
      name
    }
  }
}
veriablesJson
{
  "episode": "JEDI",
  "withAppearsIn": true,
  "withFriends": true
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

上の例で$with----の変数によって変化することが分かると思います。

@include(if: Boolean) trueのときは表示されます
@skip(if: Boolean) falseのときは表示されます

Mutations

今まではデータ取得についてでしたが今回は編集や追加についてです
GraphQLではデータの取得に重点を置いていますがもちろんサーバー側ではデータの編集も重要です。

RESTでもGETではデータを変更することは推奨されないように, GraphQLでもクエリでデータを変更することは推奨されません。
RESTではPOST, PUTなどでしたがGraphQLではmutationsが推奨されています。

request
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
variablesJson
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
response
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

リクエストされたデータに対して, 更新されたデータがレスポンスで返却されます。
createReviewreview引数が単一の値ではなくオブジェクトなことに気づくと思います。これについてはこの先のスキーマとタイプを参照してください

またqueryは並列して実行されますがmutationsは順番に実行されます。
これは1リクエストで複数のmutationsを実行しても必ず最初のものが終了してから2番目が実行されることが保証され, データが競合しないことが保証されます。

Inline Fragments

GraphQLでは型をインターフェースを用いて表現することも出来ます。(詳しくはこの先のスキーマとタイプを参照してください)

型に応じて取得するデータを変更できます

request
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
variablesJson
{
  "ep": "JEDI"
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

heroではDroidかHumanが返却され(インターフェースであるCharactorが返却されます), それに応じて変化します。

特殊なフィールド

リクエストに__typenameを含めることで返された型を参照することが出来ます。

request
{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}
response
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

またこのような特殊なものはこの先のIntrospectionを参照してください。

スキーマとタイプ

GraphQLは実装された言語に依存しないよう, GraphQL独自の型概念が存在します。

Object types and fields

GraphQLスキーマの最も基本的なコンポーネントはオブジェクト型です。
これは, サービスから取得できるオブジェクトの種類とフィールドを表します。
スキーマでは以下のように書きます。

type
type Character {
  name: String!
  appearsIn: [Episode]!
}
  • CharacterはGraphQLのオブジェクト型です. 複数のフィールドを持ちます. GraphQLのスキーマはほとんどこのオブジェクト型です.
  • nameappearsInCharacter型のフィールドです. これらはQueryから参照することが出来ます.
  • Stringは実装されたスカラー型の一つです. 詳しくはあとで.
  • String!はフィールドがnullでないことを保証します. つまり, このフィールドを参照すると常に値が返ってきます.
  • [Episode!]Episodeオブジェクトの配列を表します.またnullで無いので, 常に配列を返します.(配列の大きさが0を含む)

Arguments

GraphQLのフィールドには引数をつけることが出来ます。

ex
type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

上の例ではlengthフィールドに一つのフィールドを定義しています.
引数は必須ではなくデフォルトを設定することもでき, 上の例ではデフォルトにMETERが指定されています.

The Query and Mutation types

スキーマは通常オブジェクト型ですが, 特別な2つのスキーマがあります.

schema
schema {
  query: Query
  mutation: Mutation
}

すべてのGraphQLのサービスにはquery型があり, mutation型もあるかもしれません.
これらはすべて, クエリのエントリーポイントとして定義されます.

たとえば

request
query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}
response
{
  "data": {
    "hero": {
      "name": "R2-D2"
    },
    "droid": {
      "name": "C-3PO"
    }
  }
}

のようなクエリが実行された場合, Query型が以下のようなhero, droidフィールドを定義されている必要があります.

type
type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}

Mutationsも同様にMutation型のフィールドに定義されている必要があります.
スキーマへのエントリーポイントであるという点以外はオブジェクト型と同じで,フィールドも同じように動作します.

Scalar types

オブジェクト型には名前とフィールドが定義されますが, どこかでそのフィールドを具体的なデータに解決しなければなりません.
これがスカラー型です. GraphQLにはデフォルトで以下のスカラ型のセットが用意されています.

スカラー型名 内容
Int 符号付き32ビット整数
Float 符号付き倍精度浮動小数点値
String UTF-8文字シーケンス
Boolean trueまたはfalse
ID ユニークな値が定義され, 再取得時等にデータを検証する

もちろん自分でスカラー型を定義することもできます.

scalar Date

この型をシリアライズ, デシリアライズ, バリデートするのは内部実装に依存します.

Enumeration types

列挙型は決められた値の要素を定義しますが, その中に重複した値を宣言することは許可されません.

enum
enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

上の例はEpisode列挙型を定義し, NEWHOPE, EMPIRE, JEDIという値を宣言します.

様々な言語のGraphQL実装では, 言語に存在するEnumを利用することや, Javascriptの様にEnumが存在しないものは独自に実装するかもしれませんが, それはクライアント側には関係がなく, GraphQLとしてのenumとして完全に動作します.

Lists and Non-Null

オブジェクト型, スカラー型および列挙型はGraphQLで定義できる型です.
しかし実際に使用する場合にはこれらの他に追加の型修飾子を適用できます.

type
type Character {
  name: String!
  appearsIn: [Episode]!
}

上の例ではString型に!をつけ, null非許容型とします.

ex
query searchWord($word: String!)
variableJson
{
  "word": null
}

例えば上記の様に宣言し, 実行すると以下のような検証エラーを出力します.

error
{
  "errors": [
    {
      "message": "Variable \"$word\" of required type \"String!\" was not provided.",
      "locations": [
        {
          "line": 1,
          "column": 18
        }
      ]
    }
  ]
}

リストも同じように動作します. 例えば[String]のようにすると, String型の配列を返します.
![]は組み合わせる事もでき

ex
fieldName: [String!]

とすると, リスト自体はnullでも良いですが, null要素は存在できません.
以下のような対応になります

fieldの中身 エラーの有無
null
[]
["a", "b"]
["a", null, "b"]

非null非許容型リストを定義すると

ex
fieldName: [String]!
fieldの中身 エラーの有無
null
[]
["a", "b"]
["a", null, "b"]

のようになります.

Interfaces

他の言語と同様にGraphQLもインターフェースをサポートしています.
インターフェースはそのインターフェースを実装するために含まなければならないフィールドを定義する抽象型です.

たとえばCharacterインターフェースなら以下のようになります.

ICharacter
interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

つまりCharacterを実装するすべての型はこれらを実装する必要があります.

ex
type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}

Characterインターフェースに定義されたフィールドだけでなくtotalCredits, starships, primaryFunctionのようにその型独自のフィールドも宣言できます.

インターフェースは便利ですが使い方に注意してください.

request
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    primaryFunction
  }
}
veriablesJson
{
  "ep": "JEDI"
}
response
{
  "errors": [
    {
      "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ]
    }
  ]
}

primaryFunctionフィールドはDroid型にしか宣言されていないためエラーになります.

conditional fragment

これを回避するためにインラインフラグメントを使えばDroid型の場合のみ取得するようにできます.

requestFix
query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
newResponse
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

インラインフラグメントに関してはこちらを参照して下さい.

union types

共用体はインターフェースと非常に似ていますが, それぞれで同じフィールドを定義する必要はありません.

union
union SearchResult = Human | Droid | Starship

SearchResultHuman, Droid, Starshipのいずれかを返します.

共用体は具体的なオブジェクト型を指定する必要があり, インターフェースや他の共用体は指定できません.

上記SearchResult共用体を返すフィールド参照する場合は条件付きフラグメントを使用して参照します.

request
{
  search(text: "an") {
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}
response
{
  "data": {
    "search": [
      {
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "name": "Leia Organa",
        "height": 1.5
      },
      {
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}

Input types

これまではenumやstringと言ったスカラー型を引数としてフィールドに渡す方法を説明しました.
GraphQLではそれだけでなく, オブジェクト型を簡単に渡すこともできます.(特にmutationsでよく使います)
オブジェクト型と同じように見えますがtypeではなくinputキーワードを使用します.

input
input ReviewInput {
  stars: Int!
  commentary: String
}

以下のように使います

mutation
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
variablesJson
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
response
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

input型のフィールドにinput型を指定することはできますが, 出力はinput型と同じ形で出力されるとは限りません.
またinput型はフィールドに引数をつけることはできません.

Validation

まだとちゅう

Execution

まだとちゅう

Introspection

まだとちゅう

参考文献

Introduction to GraphQL

229
170
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
229
170