GraphQLのクエリを基礎から整理してみた

この記事はGraphQLで使われる文法の入門編です。
よく使われるクエリの文法に焦点を当てて整理しています。

今回はPlaygroundとして一般公開されている以下のサイトを例にして、GraphQLの文法をクエリの実行を試しながら整理していきます。

http://snowtooth.moonhighway.com/
![FireShot Capture 157 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/b1f8f009-aede-93ae-99bd-333b2c4764b5.png)

※以下のページより、PlaygroundのIDEをインストールして利用することも可能です。今回は既に公開されているスキーマベースで文法を確認していきます。
https://github.com/prisma/graphql-playground

## SQL/RESTとの比較

GraphQLはクエリ言語です。基本的にはQueryとMutation、Subscriptionを使い分けます。

- SQLはデータベース専用のクエリ言語です。
- GraphQLはインターネットのためのクエリ言語です。


||SQL/REST|GraphQL|
|:--|:--|:--|
|取得|SELECT|Query|
|登録|INSERT|Mutation|
|更新|UPDATE|Mutation|
|削除|DELETE|Mutation|

- Subscriptionはサーバサイドからプッシュ通知を受けるようなリアルタイム処理で使用されます。

RESTでデータを取得するときと同様に、curlによってデータを取得することができます。

```bash:curlでクエリを投げる方法
$ curl 'http://.../' -H 'Content-Type: application/json' --data '{"query": "{allLifts {name}}"}'
```

## スキーマ

Playgloundからスキーマを確認してみます。
PlayGroundの右側に`SCHEMA`という緑色のタグがあるのでクリックすると、スキーマの定義を確認することができます。


QUERIES/MUTATIONS/SUBSCRIPTIONSと大別されていて、スキーマに定義されたクエリ等の情報が記載されています。

![FireShot Capture 171 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/14795e7a-5f56-333c-5451-16bf6abc28bf.png)

例えば、Liftオブジェクトの一覧を取得したい場合は`allLifts`を使用すればよく、どのプロパティを取得できるかまで見ることが可能です。

下記でクエリの文法を見ていきます。

## フィールド

クエリ取得できるフィールドは、スカラタイプとオブジェクトタイプの2種類が存在します。

スカラタイプはプログラミング言語でいうところのプリミティブ型を意味しており、GraphQLでは以下の5種類が対応しています。

- Int
- Float
- String
- Boolean
- ID

※IDはユニークな文字列が返却されます。

オブジェクトタイプは1つ以上のフィールドを含んだグループで、JSONの入れ子として表現されます。

## シンプルなQuery

データを取得する際の最も基本的なqueryです。`allLifts`でLiftsの配列を要求し、配列内の要素としてnameを指定しています。`allLifts`は上記のQUERIESで定義されているものです。

```graphql:シンプルなクエリ①
query {
  allLifts {
    name
  }
}
```

このレスポンス結果は以下の通りです。配列の中に`name`要素のみが返却されていることがわかります。
![FireShot Capture 158 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/22914c84-7e2f-c4ef-2862-26a7f9b0c9e0.png)


配列内の要素として`status`を追加してみます。リクエストに欲しい要素を指定することでレスポンスが拡張されます。

```graphql:シンプルなクエリ②
query {
  allLifts {
    name
    status
  }
}
```

返ってくるレスポンスにstatusが追加されました。
![FireShot Capture 159 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/c58bd6fd-d6b5-664e-9e5d-2c446c1b2f0f.png)

## 複数のデータポイントに対するQuery

以下のように組み合わせることで、複数のデータポイントからの取得クエリを1つにまとめることができます。RESTの場合は別APIに対して2度リクエストを発行する必要がありますが、GraphQLの場合はこのような柔軟性により一度のリクエストで完結します。

```graphql:複数のクエリ
query liftsAndTrails {
  allLifts {
    name
    status
  }
  allTrails {
    name
    difficulty
  }
}
```

※長いのでallLiftsの配列は閉じていますが、それぞれのデータを取得することができました。
![FireShot Capture 172 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/17122d44-292d-67f0-e667-5901fac1c8cf.png)

## 一致取得

IDを指定して、1件ヒットしたデータをピンポイントで取得することができます。ORマッパーのfindByIdに相当します。

```graphql:IDを指定して取得
query jazzCatStatus {
  Lift(id: "jazz-cat") {
    id
    name
    status
    night
    elevationGain
  }
}
```

![FireShot Capture 163 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/5fe41926-339b-644e-73d3-07b7f03a798f.png)

## フィルタ

ステータスを指定することで、取得するデータをフィルタリングすることができます。

```graphql:フィルタされたデータを取得
query closedLifts {
  allLifts(status: CLOSED) {
    name
    status
  }
}
```

![FireShot Capture 162 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/4c72daf4-24d2-8c65-9df0-7a45e8dfd1fb.png)

## 件数の取得

Liftオブジェクトの件数を取得できます。今回は条件指定が必須のため、CLOSEDステータスの件数をカウントしています。

```graphql:件数の取得
query liftsAndTrails {
  liftCount(status:CLOSED)
}
```

![FireShot Capture 164 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/28925cbb-eca2-e2a5-65bd-bebcd6018752.png)


## エイリアスの指定

別名として名前を指定することもできます。

```graphql:複数のクエリリクエスト
query liftsAndTrails {
  closed: liftCount(status: CLOSED)
  alists: allLifts {
    c: name
    status
  }
  blists: allTrails {
    name
    difficulty
  }
}
```

![FireShot Capture 165 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/ac4e55a6-4c4a-1826-52b9-f31bb388dd52.png)

## 入れ子

オブジェクトを取得する場合は要素が入れ子になります。

```graphql:入れ子の取得
query liftToAccessTrail {
  Trail(id:"river-run") {
    groomed
    accessedByLifts {
      name
      capacity
    }
  }
}
```

![FireShot Capture 166 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/30fafea0-2827-7e5e-2885-108f0206bdde.png)

## フラグメント

冗長な繰り返しを避けるために、フラグメントを使用することができます。

以下では`liftInfo`として3つのプロパティをまとめることで、`Trail`と`Lift`それぞれで同じ宣言を繰り返す必要がなくなります。

```graphql:フラグメントの利用
fragment liftInfo on Lift {
  name
  night
  elevationGain
}

query {
  Trail(id: "river-run") {
    name
    difficulty
    accessedByLifts {
      ...liftInfo
    }
  }
  Lift(id: "jazz-cat") {
    ...liftInfo
    trailAccess {
      name
      difficulty
    }
  }
}
```

![FireShot Capture 167 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/59a663f9-4c49-dea4-c2ac-7583fe376025.png)

## Mutations

Mutationを使って更新が可能です。

まずは現状のステータスを確認してみます。`allLifts`を使用して変更したい要素のステータスを見てみましょう。

```graphql:
query all {
  allLifts {
    id
    name
    status
  }
}
```

![FireShot Capture 168 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/02a8b34c-0bc5-9d34-4607-3e6919cc1a11.png)

この一番上に表示されたIDが「astra-express」のステータスが現在OPENなので、CLOSEDに変更してみます。

```graphql
mutation setStatus {
  setLiftStatus(id: "astra-express" status: CLOSED) {
    id
    name
    status
  }
}
```

想定どおりCLOSEDに変更できました！
![FireShot Capture 169 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/e540e906-ef97-266b-19d9-73491a7996cd.png)

※念の為元のOPENに戻しておきましょう。

```graphql
mutation setStatus {
  setLiftStatus(id: "astra-express" status: OPEN) {
    id
    name
    status
  }
}
```

![FireShot Capture 170 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/2f8591d7-6c4e-fe66-3119-6baa36a8f1c1.png)


## 入力値バリデーション

更新する際に、バリデーションを事前に定義することが可能です。`$`文字列の後に続くものが変数となり、`:`の後に型と必須チェックを記載しています。

例えば、文字列型で必須にしたい場合は`$str:String!`、数値型で必須ではない場合は`$num:Int`となります。

※今回はスキーマ自体がID/Status共に必須ではありますが、文法の整理として確認していきます。

```graphql:バリデーション定義
mutation setStatus($id:ID! $status:LiftStatus!) {
  setLiftStatus(id:$id status: $status) {
    id
    name
    status
  }
}
```

IDを指定して、ステータスをOPENに変更します。

```json:QUERY_VARIABLE
{
  "id": "astra-express",
  "status": "OPEN"
}
```

![FireShot Capture 174 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/bbff7cde-6adf-8adc-e895-171cabbdd7f1.png)

## Subscription

subscriptionはサーバからリアルタイムの更新情報をプッシュして欲しい時に使用します。queryやmutationと違い、subscriptionはWebSocketで接続された状態を維持してサーバ側の更新を待ちます。

まずは以下を実行して待ち状態にしましょう。

```graphql
subscription {
  liftStatusChange {
    id
    name
    status
  }
}
```

別タブで同じURLを開きます。別タブからステータス更新のmutationを投げてみます。

```graphql
mutation setStatus {
  setLiftStatus(id: "astra-express" status: OPEN) {
    id
    name
    status
  }
}
```

subscriptionのタブに戻ると、更新されたことをトリガーにデータを取得していることがわかります。下記では2回ステータスをOPENに更新するmutationを投げました。

![FireShot Capture 175 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/6b1fa0d4-4ade-8a2a-7c56-492f71a71793.png)

## Introspection

スキーマで定義されているタイプにどんなものがあるかを確認したい場合は、`__schema`クエリを投げることで確認できます。

```graphql:typesの定義確認
query {
  __schema {
    types {
      name
      description
    }
  }
}
```

![FireShot Capture 176 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/28010400-fec8-f3be-07fd-5a4ab649b812.png)


特定のtypeについて詳しくみたい場合は、`__type`クエリを使用することで確認できます。
以下ではTrailのすべてのフィールドについて確認することが可能です。

```graphql
query trailType {
  __type(name:"Trail") {
    name
    fields {
      name
      description
      isDeprecated
    }
  }
}
```

![FireShot Capture 177 - Playground - http___snowtooth.moonhighwa_ - http___snowtooth.moonhighway.com_.png](https://qiita-image-store.s3.amazonaws.com/0/206255/ed7dfc46-405e-f218-194a-2b6a6c4ac4a7.png)