GraphQL

GraphQLって何?

More than 1 year has passed since last update.


まだクエリとミューテーションしか書けていないので他は参考文献よりどうぞ

(時間かかるから徐々に追記します)

(下書きでもいいけどモチベ下がりそう)



はじめに

この記事はできるだけ簡潔に記述することを目的にしています。

詳しいことを知りたい方は他をどうぞ

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


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