まだクエリとミューテーションしか書けていないので他は参考文献よりどうぞ
(時間かかるから徐々に追記します)
(下書きでもいいけどモチベ下がりそう)
はじめに
この記事はできるだけ簡潔に記述することを目的にしています。
詳しいことを知りたい方は他をどうぞ
この記事内では見やすくなるように多少データを整形しています
GraphQLとは
GraphQLはAPI向けの言語です。データの形式のみの定義のため, 言語やデータを保存する方法は依存しません。(要するにDBでもテキストでもいい)
GraphQLの定義に従ってクエリを書き, サーバーと通信を取ることでJSONになって戻ってきます。
クエリとミューテーション
GraphQLには様々なデータの取得や編集に関するものがあります。
Fields
最も簡単なのはこのFieldです。
{
hero {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
このようにリクエストしたクエリと同じ形でJsonが帰ってきます。
これがGraphQLでは重要です。(すぐに欲しいデータが受け取れる)
Stringやintといった単一のものだけでなくObjectも受け取れます。
また, コメントは先頭に#をつけて表します。
{
hero {
name
# Queries can have comments!
friends {
name
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{ "name": "Luke Skywalker" },
{ "name": "Han Solo" },
{ "name": "Leia Organa" }
]
}
}
}
上の例ではfriends
は配列のオブジェクトを返します。
Arguments
上のFieldだけでは決まったデータを受け取ることしか出来ません。
そこでArgumentsを追加することでいろいろなデータを柔軟に指定, 取得できます。
{
human(id: "1000") {
name
height
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
これまでAPIデザインとして一般的だったRESTはURLクエリなどでしかArgumentsを送れませんでした。
が、GraphQLではそれぞれのFieldなどに対してもArgumentsをつけることが出来ます。
{
human(id: "1000") {
name
height(unit: cm)
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 172
}
}
}
この引数には様々な型があります。また自分で宣言することも可能です。
(型についてはこの先のスキーマとタイプを参照してください)
Aliases
例えばhero(episode: EMPIRE)
とhero(episode: JEDI)
を同じクエリで参照しようとした場合, 同じhero
フィールドになってしまい参照できません。そこでエイリアスを利用します。
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
エイリアスを使うことで両方の結果を受け取れます。
Fragments
同じデータを受け取るのに同じものを何回も宣言するのは冗長です
{
leftComparison: hero(episode: EMPIRE) {
name
appearsIn
friends {
name
}
}
rightComparison: hero(episode: JEDI) {
name
appearsIn
friends {
name
}
}
}
フラグメンツを使うことで上の例が下のように簡単に宣言できます
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
{
"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行目)
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
変数名: 値
のように記述し,変数辞書を作成します。(Jsonのことが多い)
{
"episode": "JEDI"
}
{
"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を使うことでクエリへの引数を変更できるようにナリましたが、引数だけでなく変数によって取得するデータを変更しなければならないかもしれません。
query Hero($episode: Episode, $withAppearsIn: Boolean!, $withFriends: Boolean!) {
hero(episode: $episode) {
name
appearsIn @skip(if: $withAppearsIn)
friends @include(if: $withFriends) {
name
}
}
}
{
"episode": "JEDI",
"withAppearsIn": true,
"withFriends": true
}
{
"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が推奨されています。
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
リクエストされたデータに対して, 更新されたデータがレスポンスで返却されます。
createReview
のreview
引数が単一の値ではなくオブジェクトなことに気づくと思います。これについてはこの先のスキーマとタイプを参照してください
またqueryは並列して実行されますがmutationsは順番に実行されます。
これは1リクエストで複数のmutationsを実行しても必ず最初のものが終了してから2番目が実行されることが保証され, データが競合しないことが保証されます。
##Inline Fragments
GraphQLでは型をインターフェースを用いて表現することも出来ます。(詳しくはこの先のスキーマとタイプを参照してください)
型に応じて取得するデータを変更できます
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
{
"ep": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
heroではDroidかHumanが返却され(インターフェースであるCharactorが返却されます), それに応じて変化します。
##特殊なフィールド
リクエストに__typename
を含めることで返された型を参照することが出来ます。
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
{
"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 Character {
name: String!
appearsIn: [Episode]!
}
-
Character
はGraphQLのオブジェクト型です. 複数のフィールドを持ちます. GraphQLのスキーマはほとんどこのオブジェクト型です. -
name
とappearsIn
はCharacter
型のフィールドです. これらはQueryから参照することが出来ます. -
String
は実装されたスカラー型の一つです. 詳しくはあとで. -
String!
はフィールドがnullでないことを保証します. つまり, このフィールドを参照すると常に値が返ってきます. -
[Episode!]
はEpisode
オブジェクトの配列を表します.またnullで無いので, 常に配列を返します.(配列の大きさが0を含む)
Arguments
GraphQLのフィールドには引数をつけることが出来ます。
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
上の例ではlength
フィールドに一つのフィールドを定義しています.
引数は必須ではなくデフォルトを設定することもでき, 上の例ではデフォルトにMETER
が指定されています.
##The Query and Mutation types
スキーマは通常オブジェクト型ですが, 特別な2つのスキーマがあります.
schema {
query: Query
mutation: Mutation
}
すべてのGraphQLのサービスにはquery
型があり, mutation
型もあるかもしれません.
これらはすべて, クエリのエントリーポイントとして定義されます.
たとえば
query {
hero {
name
}
droid(id: "2000") {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
},
"droid": {
"name": "C-3PO"
}
}
}
のようなクエリが実行された場合, Query
型が以下のようなhero
, droid
フィールドを定義されている必要があります.
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 Episode {
NEWHOPE
EMPIRE
JEDI
}
上の例はEpisode
列挙型を定義し, NEWHOPE
, EMPIRE
, JEDI
という値を宣言します.
様々な言語のGraphQL実装では, 言語に存在するEnumを利用することや, Javascriptの様にEnumが存在しないものは独自に実装するかもしれませんが, それはクライアント側には関係がなく, GraphQLとしてのenumとして完全に動作します.
Lists and Non-Null
オブジェクト型, スカラー型および列挙型はGraphQLで定義できる型です.
しかし実際に使用する場合にはこれらの他に追加の型修飾子を適用できます.
type Character {
name: String!
appearsIn: [Episode]!
}
上の例ではString
型に!
をつけ, null非許容型とします.
query searchWord($word: String!)
{
"word": null
}
例えば上記の様に宣言し, 実行すると以下のような検証エラーを出力します.
{
"errors": [
{
"message": "Variable \"$word\" of required type \"String!\" was not provided.",
"locations": [
{
"line": 1,
"column": 18
}
]
}
]
}
リストも同じように動作します. 例えば[String]
のようにすると, String型の配列を返します.
!
と[]
は組み合わせる事もでき
fieldName: [String!]
とすると, リスト自体はnullでも良いですが, null要素は存在できません.
以下のような対応になります
fieldの中身 | エラーの有無 |
---|---|
null | 無 |
[] | 無 |
["a", "b"] | 無 |
["a", null, "b"] | 有 |
非null非許容型リストを定義すると
fieldName: [String]!
fieldの中身 | エラーの有無 |
---|---|
null | 有 |
[] | 無 |
["a", "b"] | 無 |
["a", null, "b"] | 無 |
のようになります.
Interfaces
他の言語と同様にGraphQLもインターフェースをサポートしています.
インターフェースはそのインターフェースを実装するために含まなければならないフィールドを定義する抽象型です.
たとえばCharacter
インターフェースなら以下のようになります.
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
つまりCharacterを実装するすべての型はこれらを実装する必要があります.
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
のようにその型独自のフィールドも宣言できます.
インターフェースは便利ですが使い方に注意してください.
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
primaryFunction
}
}
{
"ep": "JEDI"
}
{
"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
型の場合のみ取得するようにできます.
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
インラインフラグメントに関してはこちらを参照して下さい.
union types
共用体はインターフェースと非常に似ていますが, それぞれで同じフィールドを定義する必要はありません.
union SearchResult = Human | Droid | Starship
SearchResult
はHuman
, Droid
, Starship
のいずれかを返します.
共用体は具体的なオブジェクト型を指定する必要があり, インターフェースや他の共用体は指定できません.
上記SearchResult
共用体を返すフィールド参照する場合は条件付きフラグメントを使用して参照します.
{
search(text: "an") {
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
{
"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 ReviewInput {
stars: Int!
commentary: String
}
以下のように使います
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
input型のフィールドにinput型を指定することはできますが, 出力はinput型と同じ形で出力されるとは限りません.
またinput型はフィールドに引数をつけることはできません.
Validation
まだとちゅう
Execution
まだとちゅう
Introspection
まだとちゅう