Django で GraphQL 実装してみた
実装
基本的に、graphene のチュートリアルをなぞっていく.
モデルの作成
app/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField()
category = models.ForeignKey(Category, related_name='ingredients')
def __str__(self):
return self.name
マイグレート
docker compose exec app-back python manage.py makemigrations
docker compose exec app-back python manage.py migrate
テストデータ生成
app/fixtures/ingredients.json
ここからダウンロード
docker compose exec app-back python manage.py loaddata ingredients
スキーマ作成
config/scheme.py
import graphene
import app.schema
class Query(app.schema.Query, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query)
app/schema.py
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from .models import Category, Ingredient
class CategoryNode(DjangoObjectType):
class Meta:
model = Category
fields = ("name", "ingredients")
filter_fields = ["name", "ingredients"]
interfaces = (relay.Node,)
class IngredientNode(DjangoObjectType):
class Meta:
model = Ingredient
interfaces = (relay.Node,)
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"notes": ["exact", "icontains"],
"category": ["exact"],
"category__name": ["exact"],
}
class Query:
category = relay.Node.Field(CategoryNode)
categories = DjangoFilterConnectionField(CategoryNode)
ingredient = relay.Node.Field(IngredientNode)
ingredients = DjangoFilterConnectionField(IngredientNode)
以下のようにすることで、クエリの詳細設定ができる
...
"category__name": ["exact"],
"category__is_active": ["exact"],
}
+ class IngredientConnection(relay.Connection):
+ class Meta:
+ node = IngredientNode
class Query:
category = relay.Node.Field(CategoryNode)
categories = DjangoFilterConnectionField(CategoryNode)
ingredient = relay.Node.Field(IngredientNode)
- ingredients = DjangoFilterConnectionField(IngredientNode)
+ ingredients = relay.ConnectionField(IngredientNode)
+
+ def resolve_ingredients(root, info, **kwargs):
+ return Question.objects.all()
設定を追加する
config/setting.py
INSTALLED_APPS = [
...
"graphene_django", # 追加
"app", # 追加
]
GRAPHENE = {"SCHEMA": "config.schema.schema"} # 追加
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # 追加
...
]
config/urls.py
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView # 追加
from config.schema import schema # 追加
urlpatterns = [
path("admin/", admin.site.urls),
# 以下追加
path(
"graphql/",
GraphQLView.as_view(
graphiql=True,
schema=schema,
),
),
]
実行してみる
http://localhost/graphql にアクセス
左上のペインにクエリを入力する
query {
ingredients {
edges {
node {
id
name
}
}
}
}
再生ボタンを押下することで、右側ペインに結果が表示される
{
"data": {
"ingredients": {
"edges": [
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6MQ==",
"name": "Eggs"
}
},
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6Mg==",
"name": "Milk"
}
},
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6Mw==",
"name": "Beef"
}
},
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6NA==",
"name": "Chicken"
}
}
]
}
}
}
絞り込み
app/schema.py の filter_fileds で設定できる
class IngredientNode(DjangoObjectType):
class Meta:
model = Ingredient
interfaces = (relay.Node,)
filter_fields = {
"name": ["exact", "icontains", "istartswith"],
"notes": ["exact", "icontains"],
"category": ["exact"],
"category__name": ["exact"],
}
設定値 | 動作 |
---|---|
exact | 完全一致 |
icontains | 指定値を含む |
istartswith | 指定値から始まる |
category_name 完全一致の場合
リレーション先のカラム名は、パスカルケースで指定する
query {
ingredients(category_Name: "Dairy") {
edges {
node {
id
name
}
}
}
}
{
"data": {
"ingredients": {
"edges": [
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6MQ==",
"name": "Eggs"
}
},
{
"node": {
"id": "SW5ncmVkaWVudE5vZGU6Mg==",
"name": "Milk"
}
}
]
}
}
}
ページネーション
条件 | 動作 |
---|---|
first: n | n 行取得する |
offset: n | n 行移行を取得する |
after: id or cursor | 指定の id や cursor 以降を取得する |
query {
ingredients(first:2) {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
id
name
}
}
}
}
発見.GraphQL のいいところ
同時にリクエストすることが可能
:::note info
category(id: "\d")は、query categories を実行して表示された id を指定すること
:::
query {
categories{
edges {
node {
id
name
}
}
}
category(id:"\d") {
name
}
ingredients(category_IsActive: false) {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
id
name
}
}
}
}
所感
セキュリティなどは何もしていないが「だいぶいいぞ」って感じがする.
なんといっても、必要なデータのみが取れる, 複数のqueryを同時に実行できるのが良い.
grapheneはquerysetをよしなにやってくれるため、rest frameworkより簡単な印象を受けた.
次は、Mutation編