GraphQLでの認証
GraphQLサーバーと通信するには、適切なスコープを持つOAuthトークンが必要です。
Follow the steps in "Creating a personal access token" to create a token. 必要なスコープは、リクエストしようとしているデータの種類によります。 たとえば、ユーザデータをリクエストするには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のクエリは、指定したデータのみを返します。 クエリを作成する2は、フィールド内のフィールド(入れ子になったサブフィールドとも呼ばれます)を、スカラーだけを返すまで指定します。
クエリは以下のような構造になります。
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 {ここで、エッジの端にあるノードを取り出します。
IssueConnectiondocsは、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`)から構成される[入力オブジェクト](/v4/input_object/)(そのため波括弧です)になります。どの値がcontentとして使われるのかは、どのように分かるのでしょうか?
addReactionのドキュメントは、contentフィールドがReactionContentという型を持っていることを教えてくれます。GitHubのIssueでは特定の絵文字リアクションだけがサポートされているので、これはenumです。 リアクションとして使える値は以下のとおりです(いくつかの値は対応する絵文字の名前とは異なっていることに注意してください)。内容 絵文字 +1👍 -1👎 笑い😄 混乱😕 ハート❤️ 万歳🎉 ロケット🚀 目👀 -
呼び出しの残りの部分は、ペイロードオブジェクトから構成されます。 ここでは、ミューテーションを行った後にサーバーから返してほしいデータを指定します。 これらの行は、
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の呼び出しを作成する際にできることは、もっとたくさんあります。 以下は、次に見るべき場所です。