GraphQLとは
facebookが開発し、オープンソースとして公開中。
従来のリソース指向のRESTAPIにある課題を解決するため、サーバサイドへの通信に利用できるクエリー言語として開発された。
以下の特徴を備えている。
- 柔軟なインターフェース
- 単一エンドポイント
- 型システム
RESTAPIに対するメリット
Over/UnderFetch問題
RESTAPIではリソースごとにエンドボイントが作成されるため、
クライアントによっては以下の問題が発生し得る
- 必要な情報を得るために複数のAPIをコールしなければならないケース(UnderFetch)
- APIのインターフェース上、クライアントが必要以上の情報を取得してしまうケース(OverFetch)
これらのケースに対し、GraphQLでは単一のエンドポイントでクライアントが必要な情報を選択して取得できるというメリットを備えている
スキーマ
RESTAPIではAPI毎のインターフェースに合わせて、リクエストの型をクライアントに定義する必要がある
GraphQLではサーバ側でスキーマを定義するので、ドキュメントの生成が容易である
GraphQLの主な機能
query
RESTAPIでいうところのGETに相当
対象のリソースとフィールドを指定してリクエストすることで、
条件にあった情報を取得できる
mutation
POST/PUTに相当
更新対象のリソース、フィールドを指定してリクエストすることで、
対象を更新できる。
subscription
常時接続のメッセージモデルであるPublish/SubScribeのようなもの。
リソースに更新があった場合に、サーバからクライアントに通知することができる。
事前準備
環境
Python 3.6.0
Django 2.0.1
graphene-django 2.2.0
以下をインストールしておく
pip install graphene-django
アプリケーション作成
適当なプロジェクトを作成後、サンプルアプリケーションのディレクトリを作成する
今回はitemsとした。
プロジェクトディレクトリのsettingsに、graphene_djangoのパッケージとアプリのパスを追加しておく
# project/settings.py
# sample project application
INSTALLED_APPS += [
    'graphene_django',
    'items',
]
model
最小構成
# project/items/models.py
from django.db import models
class Items(models.Model):
    name = models.CharField(max_length=100)
    count = models.IntegerField()
    def __str__(self):
        return self.name
makemigrationでマイグレーションファイルを作成後
migrateでマイグレーションをしておく
Schema
# project/items/schema.py
import graphene
from graphene_django import DjangoObjectType
from items.models import Items
class Item(DjangoObjectType):
    class Meta:
        model = Items
class Query(graphene.ObjectType):
    items = graphene.List(Item)
project直下にもschemaファイルを作成する
# project/schema.py
import graphene
import items.schema
class Query(items.schema.Query,
            graphene.ObjectType):
    pass
schema = graphene.Schema(
    query=Query,
)
settingsにスキーマのパスを追加しておく
# project/settings.py
GRAPHENE = {
    'SCHEMA': 'schema.schema'
}
url
urlを設定する
GraphQLなので設定するのは単一のエンドポイントになる
# project/urls.py
from django.conf.urls import include, url
from django.contrib import admin
from graphene_django.views import GraphQLView
urlpatterns = [
    url(r'^graphql/', GraphQLView.as_view(graphiql=True)),  # <- graphql_sample
    url(r'^admin/', admin.site.urls),
]
実行
runserverして http://localhost/graphql/ にアクセスするとコンソール付きの画面が表示される
以下のqueryを実行してみる
query {
  items {
    id,
    name,
    count,
  }
}
構造は以下のような形
query ... 情報取得系クエリであることの宣言
items ... 取得対象のリソース。
id, name, count ... 取得対象のフィールド。書き換えることでレスポンスで取得する内容を変更できる。
DBとは疎通していないためこの時点では空の結果が返ってくる
{
  "data": {
    "items": null
  }
}
schemaに以下を追加する
# items/schema.py
class Query(graphene.ObjectType):
    items = graphene.List(Item)
    def resolve_items(self, info, **kwargs):
        return Items.objects.all()
実際の値が返る
{
  "data": {
    "items": [
      {
        "count": 1,
        "name": "orange"
      },
      {
        "count": 1,
        "name": "banana"
      },
      {
        "count": 20,
        "name": "apple"
      },
      {
        "count": 10,
        "name": "pineapple"
      }
    ]
  }
}
mutation
schemaに以下を追加する
# items/schema.py
class CreateItem(graphene.Mutation):
    class Arguments:
        name = graphene.String()
        count = graphene.Int()
    
    item = graphene.Field(lambda :Item)
    def mutate(self, info, name, count):
        item = Items.objects.create(name=name, count=count)
        return CreateItem(item=item)
class Mutation(graphene.ObjectType):
    create_item = CreateItem.Field()
project直下のスキーマにも追加する
class Mutation(items.schema.Mutation,
               graphene.ObjectType):
    pass
schema = graphene.Schema(
    query=Query,
    mutation=Mutation,  # 追加
)
以下の形式でリクエストすると、レコードが作成されレスポンスに登録結果が返ってくる
mutation {
  createItem(name:"grape", count:3) {
    item {
      name
      count
    }
  }
}
mutation ... 更新系のクエリであることの宣言
createItem() ... 実行クエリ。引数が更新内容になる
item ... 更新後のリソース情報
{
  "data": {
    "createItem": {
      "item": {
        "name": "grape",
        "count": 3
      }
    }
  }
}