Fields

オブジェクトの{}ブロックの中に置いた、取得したい値のことをフィールドと呼びます。

以下の例 GitHub API v4 を用いたもので、viewerというオブジェクトからloginというフィールドを取得する為のクエリです。

{
  viewer {
    login
  }
}

この結果は以下のようになります。

{
  "data": {
    "viewer": {
      "login": "nju33"
    }
  }
}

レスポンスはルートがdataな JSON で受け取ります。

複数フィールドを希望することもできます。例えばviewerにはbioemailといったフィールドも存在します。「bioもほしいなー。」、「emailはいらない」なら以下のように更新します。

{
  viewer {
    login
    bio
  }
}

何となくフィールドの後ろに,を付けたくなる人がいるかもしれませんが、これは必要ないです。ただしlogin,と付けても許容されます

Arguments

オブジェクトの{}の前に()を起き引数を設定できるものがあります。これは沢山あるオブジェクトのインスタンスから、あるオブジェクトだけを取り出す、またはフィルタリングした結果だけを取得するのに使う場合があります。

{
  user(login: "nju33") {
    id
  }
}

この結果は以下のとおりです。

{
  "data": {
    "user": {
      "id": "MDQ6VXNlcjE1OTAxMDM4"
    }
  }
}

引数は 1 つとは限りません。複数の場合は,で繋げて複数指定できます。

{
  search(query: "user:nju33", last: 3, type: USER) {
    userCount
  }
}

Aliases

時々引数が異なる同じフィールドを指定したいことがあります。そんな時以下のようにしてもfieldConflictエラーになってしまいます。

{
  user(login: "a") {
    id
  }
  user(login: "b") {
    id
  }
}

これは同じuserというキーに別々の結果を入れようとするからです。(ちなみに引数がまったく同じだと許容される)

これはそれぞれの結果を格納するキーを別の名前に変更することで回避できます。それには各フィールドの前に<alias>:を付けます。
引数を持つフィールドだけでなく、持たないフィールドにも使えます。

{
  viewer1: viewer {
    login
  }
  viewer2: viewer {
    name: login
  }
  user1: user(login: "nju33") {
    id
  }
  user2: user(login: "nju33") {
    uid:id
  }
}

この結果は以下の通りです。親フィールドにviewerNuserNという名前が付いてることが分かります。また子フィールドでもloginnameiduidとしてます。

{
  "data": {
    "viewer1": {
      "login": "nju33"
    },
    "viewer2": {
      "name": "nju33"
    },
    "user1": {
      "id": "MDQ6VXNlcjE1OTAxMDM4"
    },
    "user2": {
      "uid": "MDQ6VXNlcjE1OTAxMDM4"
    }
  }
}

Fragments

フィールドの共通化ができます。

例えば引数が異なりますが取得したい以下のようなクエリがあるとします。

{
  user1: user(login: "foo") {
    id
    bio
    email
  }
  user2: user(login: "bar") {
    id
    bio
    email
  }
}

どちらもidbioemailフィールドが含まれる構造の結果を期待してます。今avatorUrlというフィールドを追加したくなりました。

フィールドの構造がシンプルな今の内は、これに対する対応は「avatorUrlを両方に追加する」でも良いと思いますが、「fragmentで 1 箇所に定義したものを共通して使う」でも対応できます。

{
  user1: user(login: "foo") {
    ...userFragment
  }
  user2: user(login: "bar") {
    ...userFragment
  }
}

fragment userFragment on User {
  id
  bio
  email
  avatarUrl
}

fragmentの構造はfragment foo on FooTypeのような形になります。fooは共通して使う際に指定するもので、適当な名前を付けます。onの次には、idbioemailといったフィールドが所属するタイプ名を置きます。
上記でフラグメントを置いている箇所の定義はuser(login: String!): Userとなっているので、そのタイプはUserである事が分かります。

定義したものはフィールドと同じ箇所で、...を頭に付けてfragmentの名前を置いて使います。(...の後にスペースを挟んでも大丈夫)

fragmentは一部だけが共通といった場合でも使うことができます。例えば、「user2にだけcompanyフィールドを追加したい」と変更があった時以下のように使われているfragmentの下辺りに直接そのフィールドを追加できます。

{
  user1: user(login: "foo") {
    ...userFragment
  }
  user2: user(login: "bar") {
    ...userFragment
    company
  }
}

fragment userFragment on User {
  id
}

Operation name

クエリの場合、色々省くと以下のようになります。

{
  viewer {
    login
  }
}

これは頭のqueryが省かれてます。

query {
  viewer {
    login
  }
}

上記は無名なqueryになってます。query名前を付けることができ、その場合はqueryの後に置きます。
この名前は公式に習いアッパーキャメルケース( UpperCamelCase )で定義します。

query User {
  viewer {
    login
  }
}

queryの箇所は、操作によってmutationsubscriptionに置き換わります。

Variables

全体に対する変数が渡される事を期待しつつ、独自の変数を使うことができます。

オペレーション変数に関する定義は Operation name の後に置きます。以下は例です。

query Foo($name: String!) {
  user(login: $name) {
    id
  }
}

Operation name の後に()を置いて、その中に変数名とそのタイプを:で区切って置いてます。

変数名は頭に$を付けなければなりません。また使う時にもそのまま$から使います。

例の$nameに対するタイプの後に!が付いてます。こうするとその変数 Non-Null となり渡さなければならない変数になります。
基本的に!は付ける事が推奨され、付けない場合の 1 つはnullを許容する引数に渡してる場合です。

上記のクエリは変数を共に実行なりする必要があります。ここでは JSON で記述します。

{
  "name": "nju33"
}

ちなみに変数の定義時は$を付けなくても良いです。

もう 1 つは変数にデフォルト値を設定する場合です。これはタイプの後に= "default valuue"という形で設定できます。
デフォルト値を設定する場合!を付けるとエラーになってしまうので注意します。

query Foo($name: String = "nju33") {
  user(login: $name) {
    id
  }
}

Directives

変数によって構造を変更させたい時に使います。これには@include@skipというディレクティブがあります。
Directive は適当なフィールドの後に置けます。

@include(if: Boolean)は「引数がtrueな時だけそのフィールドを含める」ことができます。

{
  viewer {
    id @include(if: true)
  }
}

この結果は以下の通りです。

{
  "data": {
    "viewer": {
      "id": "MDQ6VXNlcjE1OTAxMDM4"
    }
  }
}

普通にidが含まれています。falseだとどうなるのかも見てみます。

{
  viewer {
    id @include(if: false)
  }
 }

結果からはidが消えています。

{
  "data": {
    "viewer": {}
  }
}

@skip(if: Boolean)@includeと反対の意味のものです。つまり@includeではfalseのものが@skipではtrueとして置き換えれます。

{
  viewer {
    id @skip(if: true)
  }
}

この結果は@include(if: false)のものと同じです。

ちなみに、もし親フィールドを持つフィールドに対してディレクティブを使った場合、ディレクティブの後に{}を置けます。

{
  viewer {
    repositories(first: 1) @include(if: true) {
      edges {
        cursor
      }
    }
  }
}

Mutations

queryと同じ箇所をmutationとすると、データの追加・更新・削除とそれらをした結果を取得する操作を行うことができます。

例えば以下は GitHub API v4 を使い、実際のあるリポジトリに Issue を作成するmurationです。

mutation CreateIssue {
  createIssue(input: {repositoryId: "...", title: "foo"}) {
    issue {
      title
    }
  }
}

結果は以下の通りです。

{
  "data": {
    "createIssue": {
      "issue": {
        "title": "foo"
      }
    }
  }
}

mutationの場合もqueryと同様構造はそのまま結果を取得します。

Inline Fragments

定義で複数のタイプが混ざっている場合( Union )があります。

FooというタイプはABCの Union だと仮にします。そしてABnameというフィールドが含まれてますが、Cにはそれが無いといいうような場合、Cの時に解決できないのでnameフィールドを指定できません。

# 仮
{
  foo: {
    name
  }
}

こんな場合は Union を分解して「Aの時はこのフィールド」...「Cの時はこのフィールド」と別々に構造を決める仕組みがあります。これは Inline Fragment と呼ばれます。

Inline Fragment は ... on Type { ... }という形になります。TypeにはA|B|C{}の中にはそれらが持つフィールドを記述します。

上の例を動くように書き直すと以下のようになります。

{
  foo: {
    ... on A {
      name
    }

    ... on B {
      name
    }

    ... on C {
      # ...
    }
  }
}

GitHub API v4 での例だとこんな実例があります。

{
  search(query: "foo", type: REPOSITORY, first: 1) {
    edges {
      node {
        ... on Repository {
          id
          name
        }
      }
    }
  }
}