GraphQLでの認証
GraphQLサーバーと通信するには、適切なスコープを持つOAuthトークンが必要です。
トークンを作成するには、「個人アクセストークンを作成する」のステップに従ってく� さい。 必要なスコープは、リクエストしようとしているデータの種類によります。 たとえば、ユーザデータをリクエストするにはUserスコープを選択してく� さい。 リポジトリ情� �にアクセスする必要があるなら、適切なRepositoryスコープを選択してく� さい。
以下のスコープをおすすめします。
user
public_repo
repo
repo_deployment
repo:status
read:repo_hook
read:org
read:public_key
read:gpg_key
リソースが特定のスコープを必要とするなら、APIは通知してくれます。
GraphQLのエンドポイント
REST APIは多数のエンドポイントを持ちますが、GraphQL APIは単一のエンドポイントを持ちます。
http(s)://[hostname]/api/graphql
行う操作にかかわらず、エンドポイントは一定のままです。
GraphQLでの通信
GraphQLの操作は複数行のJSONからなるので、GitHubはGraphQLの呼び出しを行うのにExplorerを使うことをおすすめします。 cURLや、その他の任意のHTTPを使うライブラリも利用できます。
RESTでは、HTTPの動詞によって行う操作が決まります。 GraphQLでは、クエリを実行しているのかミューテーションを実行しているかにかかわらず、JSONエンコードされたボディを提供するので、HTTPの動詞はPOST
です。 例外はイントロスペクションクエリで、これはエンドポイントへのシンプルなGET
です。 GraphQLとRESTの比較に関する詳しい情� �については「RESTからGraphQLへの移行」を参照してく� さい。
cURLを使ってGraphQLのクエリを行うには、JSONのペイロードを持つPOST
リクエストを作成してく� さい。 このペイロードには、query
という文字列が含まれていなければなりません。
curl -H "Authorization: bearer token" -X POST -d " \
{ \
\"query\": \"query { viewer { login }}\" \
} \
" http(s)://[hostname]/api/graphql
ノート: "query"
の文字列値は、改行文字をエスケープしていなければならず、そうなっていなかった� �合にはスキーマが正しくパースできません。 POST
のボディについては、外側をダブルクオートで囲み、内部のダブルクオートはエスケープしてく� さい。
クエリ及びミューテーション操作について
GitHubのGraphQL APIで許されている操作は、クエリとミューテーションの2種類です。 GraphQLをRESTと比較すると、クエリはGET
リクエストのような操作で、ミューテーションはPOST
/PATCH
/DELETE
のような操作です。 ミューテーション名が、どの変更が実行されるのかを決定します。
レート制限に関する情� �については「GraphQLのリソース制限」を参照してく� さい。
クエリとミューテーションは似た形式を持っていますが、重要な違いがあります。
クエリについて
GraphQLのクエリは、指定したデータのみを返します。 クエリを作成するには、フィールド内のフィールド(入れ子になったサブフィールドとも呼ばれます)を、スカラー� けを返すまで指定します。
クエリは以下のような構� になります。
query { JSON objects to return }
実際の例については「クエリの例」を参照してく� さい。
ミューテーションについて
ミューテーションを作成するには、3つのことを指定しなければなりません。
- ミューテーション名。 実行したい変更の種類です。
- 入力オブジェクト。 サーバーに送信したいデータで、入力フィールドから構成されます。 これはミューテーション名に引数として渡してく� さい。
- ペイロードオブジェクト。 サーバーから返して欲しいデータで、返値フィールドから構成されます。 これは、ミューテーション名のボディとして渡してく� さい。
ミューテーションは以下のような構� になります。
mutation { mutationName(input: {MutationNameInput!}) { MutationNamePayload } }
この例の入力オブジェクトはMuitationNameInput
であり、ペイロードオブジェクトは MuitationNamePayload
です。
ミューテーションの参照では、リストされた入力フィールドは、入力オブジェクトとして渡すものです。 リストされている返値フィールドは、ペイロードオブジェクトとして渡すものです。
実際の例については「ミューテーションの例」を参照してく� さい。
変数の扱い
変数はクエリをより動的かつ強力にするもので、ミューテーションの入力オブジェクトを渡す際の複雑さを引き下げてくれます。
ノート: Explorerを使っている� �合は、変数を個別のクエリ変数ペインに入力するようにして、JSONオブジェクトの前にvariables
という語を含めないようにしてく� さい。
以下は、1つの変数を持つクエリの例です。
query($number_of_repos:Int!) {
viewer {
name
repositories(last: $number_of_repos) {
nodes {
name
}
}
}
}
variables {
"number_of_repos": 3
}
変数を利用するには3つのステップがあります。
-
操作の外部で
variables
オブジェクト中に変数を定義します。variables { "number_of_repos": 3 }
オブジェクトは有効なJSONでなければなりません。 この例はシンプルな
Int
変数型を示していますが、入力オブジェクトのようなもっと複雑な変数型を定義することもできます。 ここで複数の変数を定義することもできます。 -
変数を操作に引数として渡します。
query($number_of_repos:Int!){
この引数はキー/値ペアで、キーは
$
で始まる名前(たとえば$number_of_repos
)であり、値は型(たとえばInt
)です。 型が必� �であることを示すには!
を� えてく� さい。 複数の変数を定義した� �合は、それらをここで複数の引数として含めてく� さい。 -
変数を操作の中で利用してく� さい。
repositories(last: $number_of_repos) {
この例では、変数を取得するリポジトリ数に置き換えています。 GraphQLは強い型付けを強制するので、ステップ2で型を指定しています。
このプロセスでクエリの引数は動的になります。 これで、単純にvariables
オブジェクトの値を変更して、それ以外のクエリを同じままに保てるようになります。
変数を引数として使うことで、クエリを変更することなくvariables
オブジェクト内の値を動的に更新できるようになります。
クエリの例
もっと複雑なクエリを見ていき、これらの情� �を流れの中で捉えていきましょう。
以下のクエリはoctocat/Hello-World
リポジトリをルックアップし、最も最近にクローズされた20個のIssueを見つけ、それぞれのIssueのタイトル、URL、最初の5つのラベルを返します。
query {
repository(owner:"octocat", name:"Hello-World") {
issues(last:20, states:CLOSED) {
edges {
node {
title
url
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
この構� を1行ずつ見ていきましょう。
-
query {
データを変更するのではなく、サーバーからデータを読み取りたいので、
query
がルート操作になります。 (操作を指定しなかった� �合、query
はデフォルトでもあります) -
repository(owner:"octocat", name:"Hello-World") {
クエリを始めるにあたって、見つけたいのは
repository
オブジェクトです。 スキーマの検証によって、このオブジェクトが引数としてowner
とname
を必要とすることが示されます。 -
issues(last:20, states:CLOSED) {
リポジトリ中のすべてのIssueを扱うために、
issues
オブジェクトを呼びます。 (repository
上の単一のissue
に対するクエリを実行することも可能ですが、そのためには返して欲しいIssueの数を知り、それを引数として提供しなければなりません)以下は
issues
オブジェクトに関する詳細です。- docsは、このオブジェクトが
IssueConnection
という型を持つことを示します。 - スキーマ検証によって、このオブジェクトが引数として
last
もしくはfirst
の結果数を必要とすることが示されるので、20
を渡します。 - docsは、このオブジェクトが引数として
states
も取ることを示します。これはenumのIssueState
で、値としてOPEN
かCLOSED
を取ります。 クローズされたIssue� けを見つけるために、states
キーにCLOSED
という値を渡します。
- docsは、このオブジェクトが
-
edges {
IssueConnection
という型を持っているので、issues
はコネクション� ということが分かっています。 個々のIssueに関するデータを取り出すためには、edges
を通じてノードにアクセスしなければなりません。 -
node {
ここで、エッジの端にあるノードを取り出します。
IssueConnection
docsは、IssueConnection
型の端にあるノードがIssue
オブジェクトであることを示しています。 -
Issue
オブジェクトを取り出そうとしていることが分かっているので、docsを見て返してほしいフィールドを指定できます。title url labels(first:5) { edges { node { name } } }
ここでは、
Issue
オブジェクトのtitle
、url
、labels
フィールドを指定しています。labels
フィールドはLabelConnection
という型を持っています。issues
オブジェクトと同じように、labels
はコネクションなので、そのエッジを経て接続されたノードであるlabel
オブジェクトに到達しなければなりません。 このノードでは、返してほしいlabel
オブジェクトフィールドを指定できます。ここではname
です。
OctocatのパブリックなHello-World
リポジトリに対してこのクエリを実行しても、多くのラベルは返されないことに気づくかもしれません。 ラベルを使っている自分自身のリポジトリに対してこれを実行してみれば、違いがわかるでしょう。
ミューテーションの例
ミューテーションでは、まずクエリを実行して見なければ分からない情� �が必要になることがよくあります。 この例では2つの操作を示します。
- IssueのIDを取得するクエリ。
- 絵文字のリアクションをそのIssueに追� するミューテーション。
query FindIssueID {
repository(owner:"octocat", name:"Hello-World") {
issue(number:349) {
id
}
}
}
mutation AddReactionToIssue {
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
reaction {
content
}
subject {
id
}
}
}
名前を付ければ(この例ではFindIssueID
とAddReactionToIssue
)、同じExplorerのウィンドウ内にクエリとミューテーションを置くことができますが、それらの操作はGraphQLのエンドポイントへの個別の呼び出しとして実行されます。 クエリをミューテーションと同時に、あるいはミューテーションとクエリを同時に実行することはできません。
例を見ていきましょう。 このタスクはシンプルに見えます。絵文字のリアクションをIssueに� える� けです。
それでは、クエリから始めることはどのように知ることができるのでしょうか? この時点ではま� わかりません。
サーバー上のデータを変更したい(絵文字をIssueに添付する)ので、まずは役に立つミューテーションを探してスキーマを検索することから始めます。 リファレンスのドキュメントは、 addReaction
ミューテーションにAdds a reaction to a subject.
という説明を付けています。完璧です!
このミューテーションのドキュメントには、3つの入力フィールドがリストアップされています。
clientMutationId
(String
)subjectId
(ID!
)content
(ReactionContent!
)
!
は、subjectId
及びcontent
が必� �のフィールドであることを示しています。 content
が必� �なのは妥当です。リアクションを追� したいので、使う絵文字を指定しなければなりません。
しかしなぜsubjectId
が必� �なのでしょうか? これは、どのリポジトリ内の_どの_Issueに対応するのかを識別する唯一の方法がsubjectId
� からです。
これが、ID
を取得するためのクエリでこの例を始めなければならない理由です。
クエリを1行ずつ調べていきましょう。
-
query FindIssueID {
ここではクエリを実行します。その名前を
FindIssueID
とします。 クエリに名前を付けるのはオプション� ということに注意してく� さい。ここでは、ミューテーションと同じExplorerウィンドウに置けるように名前を付けています。 -
repository(owner:"octocat", name:"Hello-World") {
repository
オブジェクトに引数としてowner
とname
を渡すことでクエリを実行し、リポジトリを特定しています。 -
issue(number:349) {
issue
オブジェクトにnumber
を引数として渡してクエリを行うことによって、対応するIssueを特定します。 -
id
ここで、
subjectId
として渡すhttps://github.com/octocat/Hello-World/issues/349
のid
を取り出します。
このクエリを実行すると、id
: MDU6SXNzdWUyMzEzOTE1NTE=
が得られます。
ノート: このクエリが返すid
は、ミューテーション中でsubjectID
として渡す値です。 ドキュメントも、スキーマのイントロスペクションでもこの関係は示されません。このことを理解するには、名前の背景となっている概念を理解しなければなりません。
IDが分かれば、ミューテーションで先に進むことができます。
-
mutation AddReactionToIssue {
ここでミューテーションを実行します。
AddReactionToIssue
という名前を付けます。 クエリと同じように、ミューテーションに名前を付けることはオプションです。ここではクエリと一緒に同じExplorerウィンドウに置けるように名前を付けています。 -
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
この行を調べましょう。
-
addReaction
はミューテーションの名前です。 -
input
は必� �の引数のキーです。 ミューテーションではこれは常にinput
になります。 -
{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}
は必� �の引数の値です。 ミューテーションでは、これは常に入力フィールド(このケースではsubjectId
とcontent
)から構成される入力オブジェクト(そのため波括弧です)になります。どの値がcontentとして使われるのかは、どのように分かるのでしょうか?
addReaction
のドキュメントは、content
フィールドがReactionContent
という型を持っていることを教えてくれます。GitHubのIssueでは特定の絵文字リアクション� けがサポートされているので、これはenumです。 リアクションとして使える値は以下のとおりです(いくつかの値は対応する絵文字の名前とは異なっていることに注意してく� さい)。内容 絵文字 +1
👍 -1
👎 笑い
😄 混乱
😕 heart
❤️ 万歳
🎉 ロケット
🚀 目
👀
-
-
呼び出しの残りの部分は、ペイロードオブジェクトから構成されます。 ここでは、ミューテーションを行った後にサーバーから返してほしいデータを指定します。 これらの行は、
addReaction
のドキュメントから来ています。指定できる返値フィールドは3つあります。clientMutationId
(String
)reaction
(Reaction!
)subject
(Reactable!
)
この例では、2つの必� �フィールド(
reaction
及びsubject
)を返します。これらはどちらも必� �のサブフィールドを持っています(それぞれcontent
とid
)。
このミューテーションを実行すると、レスポンスは次のようになります。
{
"data": {
"addReaction": {
"reaction": {
"content": "HOORAY"
},
"subject": {
"id": "MDU6SXNzdWUyMTc5NTQ0OTc="
}
}
}
}
これで完了です。 🎉の上にホバーして自分のユーザ名を見つけ、Issueへのリアクションを確認してく� さい。
最後に一つ注意です。インプットオブジェクト中で複数のフィールドを渡す� �合、構文が扱いにくくなることがあります。 フィールドを変数に移すと役立つかもしれません。 以下では、オリジナルのミューテーションを変数を使って書き換えています。
mutation($myVar:AddReactionInput!) {
addReaction(input:$myVar) {
reaction {
content
}
subject {
id
}
}
}
variables {
"myVar": {
"subjectId":"MDU6SXNzdWUyMTc5NTQ0OTc=",
"content":"HOORAY"
}
}
先ほどの例では、content
のフィールド値(これはミューテーション中で直接使われています)で、HOORAY
の周りにクオートがありませんが、変数で使われる� �合にはクオートがあることに気づいたかもしれません。 これには理由があります。
- ミューテーション中で
content
を直接使う� �合には、スキーマはその値がReactionContent
型であることを期待しています。これは文字列ではなく列挙型です。 スキーマ検証は、列挙値の周りにクオートを� えるとエラーを投げます。これはクオートが文字列のために予約されているからです。 content
を変数中で使う� �合、変数のセクションは適切なJSONでなければならないので、クオートが必要になります。 スキーマ検証は、変数が実行時にミューテーションに渡されるとき、ReactionContent
型を正しく解釈します。
列挙型と文字列の違いに関する詳しい情� �については、公式のGraphQL仕様を参照してく� さい。
参考リンク
GraphQLの呼び出しを作成する際にできることは、もっとたくさんあります。 以下は、次に見るべき� �所です。