これはなに?
オイラリー・ジャパンが発売している「[初めてのGraphQL](https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEGraphQL-%E2%80%95Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B6%E6%96%B0%E4%B8%96%E4%BB%A3API-Eve-Porcello/dp/487311893X/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=HWFKUTKBV7BK&dchild=1&keywords=%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEgraphql&qid=1632112193&sprefix=%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEGra%2Caps%2C250&sr=8-1)」で学んだ内容をまとめる記事です。読み進めるごとに内容を追加していきます。目次
1章_GraphQLへようこそ
2章_グラフ理論
3章_GraphQLの問い合わせ言語
4章_スキーマ設計
5章_GraphQLサーバーの実装
6章_GraphQLクライアントの実装
7章_GraphQLの実戦投入にあたって
1章_GraphQLへようこそ
1.1 GraphQLとは
- インターネットが広く普及しても、「特定の場所にあるデータをいかにして素早く読み込むか」という問題がつきまとう
- 早く・快適なアプリケーションの方が多くの人々を引き込むから
- GraphQL
- APIを利用する際に使う問い合わせ言語
- 必要なデータだけリクエストの際に指定するのが特徴
- クエリが入れ子になっている場合は記載されたデータを一回のリクエストで受け取れる
例. Star Wars APIというサーバーでGraphQLを使う
【リクエスト文】
personIDが5のデータのうち、『name』『birthYear』『created』の情報のみを取得したい
query {
person(personID:5){
name
birthYear
created
}
}
【レスポンス文】
{
"data": {
"person": {
"name": "Leia Organa",
"birthYear": "19BBY",
"created": "2014-12-10T15:20:09.791000Z"
}
}
}
-
GraphQLの言語仕様
- 使用する言語に指定はない
- JavaScriptでの実装例はGitHubで公開されている
-
GraphQLの設計原則
- 階層構造
- クエリ(データベースへの命令文)は階層構造で記述する
- フィールドは他のフィールドの入れ子
- クエリとレスポンスは同じ構造
- プロダクト中心
- GraphQLは必要とされている言語や実行時間に応じて実装する
- 強い型付け
- それぞれのフィールドが型を持つ
- クライアントごとのクエリ
- クライアントが欲する機能を提供する
- 自己参照
- GraphQLは自身の型システムに問い合わせることができる
- 階層構造
1.2 GraphQLの誕生
-
Facebookのエンジニアが自身のモバイルアプリを再設計する際に開発
- APIとFQL(FacebookのSQL)を使っていたもののパフォーマンスに問題があった
-
データ通信の変遷
- RPC
- クライアントが遠隔地のコンピュータに要求をすることで特定の処理を実行し、データを受け取る
- クライアント/サーバーモデルと情報の流れは同じ
- SOAP
- xml形式でエンコードしたメッセージを通信する
- 型システムを導入していたため、レスポンスがわかりやすかった
- 実装が複雑
- REST
- GET,PUT,POST,DELETEという操作を実行することでサーバーの状態を操作する
- URIとクライアントが欲する情報が対応している
- RPC
-
RESTの問題点
- 過剰なレスポンスデータ取得
- RESTでは必要以上の情報をレスポンスで取得してしまい、アプリケーションの高速性を妨害する
- 過小なレスポンスデータ取得
- 必要なデータを取得できるように複数のエンドポイントでリクエストを実行する場合がある
- 複数のエンドポイントにリクエストを投げることで受け取るデータ量が更に増える
- エンドポイントの管理
- 新たな機能を実装する際に「既に存在するエンドポイントを整理する」作業が発生するため、開発速度が低下する
- 過剰なレスポンスデータ取得
-
GraphQLを「RESTにおける一つのエンドポイント」として実装することもできる
-
GraphQLのカンファレンス
2章_グラフ理論
- グラフは身の回りの至る所に存在する
- データ同士の関係性を図示するのに優れている
2.1グラフ理論の用語
-
ノード
- 各データの要素の部分(下図の丸の部分)
-
エッジ
- 各データ同士のつながりの部分(下図の青線の部分)
-
数式でグラフを記述
- Gが一つのグラフを表す
- Vがノードの要素を表す
- Eがエッジの要素を表す
G=(V, E)\\
V =\{1, 2, 3, 4, 5\}\\
E = \{\{1,2\},\{1,3\},\{1,5\},\{2,3\},\{2,4\},\{2,5\},\{3,4\},\{4,5\}\}

- グラフの種類
- 無向グラフ(上図)
- エッジに方向性がない
- 有向グラフ(下図)
- エッジに方向性がある
- 数式で表す際にも方向性を配慮する必要がある
- 無向グラフ(上図)
G=(V, E)\\
V =\{1, 2, 3, 4, 5\}\\
E = (\{1,3\},\{1,5\},\{2,4\},\{2,5\},\{3,2\},\{4,3\})

2.1グラフ理論の歴史
-
ケーニヒスベルクの七つの橋問題(Wikipedia)
- 橋で繋がった陸地があるときに、同じ橋を渡らずに全ての陸地を踏破できるか、を証明する問題
- レオンハルトオイラーが地形をグラフに置き変えることで、ケーニヒスベルクの橋では踏破できないことを証明した
-
オイラーの定理(Wikipedia)
- 以下の条件を持つグラフを「オイラーグラフ」と言い、一筆書きが可能である
- グラフの全ての頂点の次数が偶数
- グラフの頂点のうち、次数が奇数であるものがちょうど2つ
- 以下の条件を持つグラフを「オイラーグラフ」と言い、一筆書きが可能である
2.3 木というグラフ
- 木
- ノードが階層的に並べられているグラフ
- 例. 組織図, 家系図
- ノードが階層的に並べられているグラフ
- 根
- 最上位に存在するノード
- エッジにより繋がっている2つノードのうち、根に近いノードを「親」、遠いノードを「子」という
- 子ノードがないノードを「葉」という
- 部分木
- 木構造の中に存在する部分的な木構造
- 例. HTMLでいうところのbodyタグなど
- 二分木
- 子がたかだか二つしかない木のこと
- 条件をつけてグラフ化することでより高速に求めるノードまでたどり着ける
2.4 現実世界のグラフ
- Twitter
- 有向グラフ
- AがBをフォローしていても、BがAをフォローしていない場合が存在するため
- Facebook
- 無向グラフ
- ユーザー同士が相互にフォローするため、ノードに方向性がない
3章_GraphQLの問い合わせ言語
- SQL
- データベースに格納されているデータを操作するための言語
- 少数のコマンドでデータを操作できる(SELECT, INSERT, UPDATE, DELETE)
- RESTにもこの思想が引き継がれている
- データへの操作(GET, POST, PUT, DELETE)
- 操作対象の指定はエンドポイント
- 例.
GET /user/1
:ユーザーデータを取得(GET)する
- RESTにもこの思想が引き継がれている
- SQLとGraphQLの違い
- 使う環境
- SQL:データベース上
- GraphQL:API上
- 構文
- SQL:INSERT, UPDATE, DELETE
- GraphQL:Mutation
- 通信によるデータの変更を監視する機能(Subscription)がGraphQLにある
- 使う環境
3.1 GraphQL APIの便利なツール
- GraphiQL(GitHub)
- ブラウザ上で動作するGraphQL APIのための統合開発ツール
- 入力補完や構文エラーなどを表示してくれる

- GraphQL Playground(GitHub)
- GraphiQLと異なる点
- ヘッダーを書き換えることができる(5章で詳細)
- デスクトップ版もHomebrewでインストールできる
- URLで共有することも可能
- GraphiQLと異なる点
- 公開GraphQL API
-
Star Wars API
- Faceboolが作成
- Star Wars APIをラップして作られた
-
GitHub API
- GitHubの情報を閲覧/更新できる
- クエリとMutationが可能
-
Yelp
- クエリの作成と実行が可能
-
Star Wars API
3.2 GraphQLのクエリ
以降で使用するGraphQL APIサイト→ここ
-
GraphQLの用語(参考になりそうなQuita)
- ルート型
- クエリの最初に指定されているやつ
- 例. query, mutation
- データソースに対して行う操作を表現している
- ドキュメントに各ルート型に対して実行できるフィールドが記載されている
- クエリの最初に指定されているやつ
- フィールド
- クエリで取得することができるデータソース
- ドキュメントにフィールドの種類や要素が記載されている
- 選択セット
- クエリ文で
{...}
の記号で囲んだ部分 - ドキュメントに「選択セットで指定できるフィールド」が記載されている
- クエリ文で
- ルート型
-
サーバーからのレスポンスはクエリで指定したフィールドと同じものになる
リクエスト(allLifts
のフィールドを実行)
query{
allLifts{
name
status
}
}
レスポンス
{
"data": {
"allLifts": [
{
"name": "Astra Express",
"status": "OPEN"
},
・・・・・・・・・・・・・・・・・・・・・・・・
- クエリを同時に複数実行したい場合には一つのクエリにまとめる必要がある
- フィールドは必要なものだけ記載すれば良い
- フィールド名は
指定する名前: 変更したいフィールド名
と書くことで変更できる(エイリアス)
リクエスト(allLifts
とallTrails
のフィールドを実行)
query {
allLifts {
name
status
}
nya_n: allTrails {
name
status
}
}
レスポンス
{
"data": {
"allLifts": [
{
"name": "Astra Express",
"status": "OPEN"
},
・・・・・・・・・・・・・・・・・・・・・・・・
],
"nya_n": [ #ここが「allTrails」から「nya_n」になっている
{
"name": "Blue Bird",
"status": "CLOSED"
},
・・・・・・・・・・・・・・・・・・・・・・・・
- クエリに引数を記載することでレスポンスのデータを絞ることができる
リクエスト(allLifts
でstatus
がOPENのものだけを表示)
query {
allLifts (status: OPEN) {
name
status
}
}
レスポンス
{
"data": {
"allLifts": [
{
"name": "Astra Express",
"status": "OPEN"
},
・・・・・・・・・・・・・・・・・・・・・・・・
- オブジェクト型の中にオブジェクトを入れると、上位のオブジェクトに関連した詳細データを得るためのクエリを書くことが出来る(以下の例ではLift型の中にTrain型が内包されている)
リクエスト(jazz-cat
というリフトが行くことができるスキーコースを検索)
query {
Lift (id: "jazz-cat") {
name
capacity
trailAccess{
name
difficulty
}
}
}
レスポンス
{
"data": {
"Lift": {
"name": "Jazz Cat",
"capacity": 2,
"trailAccess": [
{
"name": "Goosebumps",
"difficulty": "advanced"
},
・・・・・・・・・・・・・・・・・・・・・・・・
- フラグメント
- クエリ内で繰り返して記述する部分があればfragmentを使うことで、表現を使いまわすことが出来る
- 定義方法
fragment フラグメント名 on fragment内の型{hoge}
- query内で
...フラグメント名
と記載することで使える - フラグメント内で「最初に定義した以外の型」を使うことはできない
リクエスト(jazz-cat
リフトと'river-run'のスキーコース情報を要求)

query {
Lift (id: "jazz-cat") {
...liftInfo
trailAccess{
...trainInfo
}
}
Trail(id: "river-run"){
...trainInfo
accessedByLifts{
...liftInfo
}
}
}
fragment liftInfo on Lift{
name
status
capacity
night
elevationGain
}
fragment trainInfo on Trail{
name
difficulty
}
レスポンス
{
"data": {
"Lift": {
"name": "Jazz Cat",
"status": "OPEN",
"capacity": 2,
"night": false,
"elevationGain": 1230,
"trailAccess": [
{
"name": "Goosebumps",
"difficulty": "advanced"
},
・・・・・・・・・・・・・・・・・・・・・・・・
"Trail": {
"name": "River Run",
"difficulty": "intermediate",
"accessedByLifts": [
{
"name": "Jazz Cat",
"status": "OPEN",
"capacity": 2,
"night": false,
"elevationGain": 1230
・・・・・・・・・・・・・・・・・・・・・・・・
- fragmentはインラインで記述することも可能(
...on fragment内の型{hoge}
) - ユニオン型のフィールドでfragmentを使う場合は、選択セット内に該当する型をそれぞれ記載することで個別に定義ができる
リクエスト(複数の型を持つユニオン型のフィールドAgendaItem
を取得する)
query{
agenda{
...on Workout{
name
reps
},
...on Studygroup{
name
subject
students
}
}
}
3.3 Mutation
- mutation
- 書き込みの操作を行うルート型
- どのような操作ができるかはAPIスキーマで定義されている
リクエスト(jazz-cat
名前の曲を登録し、idとnameをレスポンスとして受け取る)
mutaion {
addSong(title: "jazz-cat", singer: "crazy cat", genre: "jazz") {
id
name
}
}
レスポンス
{
"data": {
"addSong": {
"id": "doweaghdcoas",
"name": "jazz-cat"
}
}
- クエリ変数
- mutaionの引数に変数を利用したい場合には以下のように書く
リクエスト(変数today_top_song_title
に入っている文字列をmutationで利用する)
mutaion ($today_top_song_title:String!) { #ここで変数の型を指定する
addSong(title: "$today_top_song_title", singer: "crazy cat", genre: "jazz") {
id
name
}
}
{
"today_top_song_title": "take five"
}
3.4 Subscription
- Subscription
- サーバーが更新された時にクライアントにその情報を提供する
- Facebookの「いいね」の自動更新
リクエスト(リフト状態が変更された時にname,capacity,statusを受け取る)
subscription{
liftStatusChange{
name
capacity
status
}
}
レスポンス(他のタブでリフトの状態を変更する)
{
"data": {
"liftStatusChange": {
"name": "Astra Express",
"capacity": 3,
"status": "HOLD"
}
}
}
3.5 Introspection
- Introspection
- APIのスキーマ(APIの使い方)を取得することができる
- queryで
__schema
を指定することで確認できる
リクエスト(query, mutation, subscriptionで使えるフィールドを全て受け取る)
query{
__schema{
queryType{
...typeFields
}
mutationType{
...typeFields
}
subscriptionType{
...typeFields
}
}
}
fragment typeFields on __Type{
name
fields{
name
}
}
レスポンス
{
"data": {
"__schema": {
"queryType": {
"name": "Query",
"fields": [
{
"name": "allLifts"
},
{
"name": "allTrails"
},
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
"mutationType": {
"name": "Mutation",
"fields": [
{
"name": "setLiftStatus"
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
"subscriptionType": {
"name": "Subscription",
"fields": [
{
"name": "liftStatusChange"
},
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
}
3.6 抽象構文木
- 抽象構文木
- 意味に関係にある情報のみを取り出した木構造
- GraphQL APIにクエリを送信すると文字列は抽象構文木にパースされる
4章_スキーマ設計
近日、公開予定
5章_GraphQLサーバーの実装
近日、公開予定
6章_GraphQLクライアントの実装
近日、公開予定
7章_GraphQLの実戦投入にあたって
近日、公開予定