GraphQLを使ってTODOアプリを作る
2024-05-14はじめに
今回はPythonのFastAPIとStrawberryを使ってGraphQL APIを開発する方法を解説します。 Todoアプリの開発を通してGraphQL APIを開発する方法を学びます。
GitHubリポジトリはこちらにあります。ブランチが3つあり、それぞれが章での作業と連動しています。
目次
対象読者と非対称読者
対象読者
- GraphQLについてある程度知っている人
- Pythonの基礎的な構文について知っている人
- PythonでGraphQLサーバーを作ってみたい人
非対称読者
- GraphQLを知らない
- Pythonを知らない
環境
- MacOS : Sonoma 14.2.1
- Python : 3.11.4
- FastAPI: 0.111.0
環境の準備
適当な作業ディレクトリで以下コマンドを実行
pip install fastapi 'uvicorn[standard]' strawberry-graphql
0: PythonでGraphQLを開発できるライブラリ
DjangoやFlask, FastAPIなどWebAPIを開発できるPythonのフレームワークが複数あるように、GraphQL APIを開発するフレームワークもいくつか存在します。
- Graphene : 最も広く使われているライブラリの一つ
- Ariadne : スキーマファースト開発を目的として構築されたライブラリ
- Strawberry : スキーマモデルを簡単に実装できるようにするライブラリ
- Tartiflette: スキーマファーストアプローチを使って構築できる
今回はFastAPIと相性の良いStrawberryを使ってGraphQL APIを作っていきます。
1: とりあえずサーバー作る
まずはサーバーを立てるところから始めます。ブランチは1-create-serverです。
import strawberry
from fastapi import FastAPI
from strawberry.asgi import GraphQL
# (1)型の定義
@strawberry.type
class Todo:
title: str
# (2)クエリの定義
@strawberry.type
class Query:
# (3)リゾルバを作成
@strawberry.field
def todo(self) -> Todo:
return Todo(title='first todo')
# (4)スキーマ定義
schema = strawberry.Schema(query=Query)
# (5)アプリケーションの起動
graphql_app = GraphQL(schema)
app = FastAPI()
app.add_route('/graphql', graphql_app)
型の定義
# (1)型の定義
@strawberry.type
class Todo:
title: str
まずは型の定義です。今回はTodoを定義しています。まずはStringのTitleのみ定義しています。
strawberryではIntやBoolean, IDなどの方がサポートされています。
参考:https://strawberry.rocks/docs/general/schema-basics#scalar-types
クエリ・リゾルバの定義
# (2)クエリの定義
@strawberry.type
class Query:
# (3)リゾルバを作成
@strawberry.field
def todo(self) -> Todo:
return Todo(title='first todo')
クエリとリゾルバの定義を行います。
@strawberry.filedを使って、リゾルバを作成します。
今回はfirst todoというタイトルのTodoを返却しています。
スキーマ定義とアプリの起動
# (4)スキーマ定義
schema = strawberry.Schema(query=Query)
# (5)アプリケーションの起動
graphql_app = GraphQL(schema)
app = FastAPI()
app.add_route('/graphql', graphql_app)
最後にスキーマ定義とアプリケーションの起動を行っています。
サーバーの起動
以下コマンドでサーバーを起動します。
uvicorn main:app --reload
localhost:8000にアクセスして、クエリを実行すると結果が得られます。
query{
todo{
title
}
}
{
"data": {
"todo": {
"title": "first todo"
}
}
}
2: Mutationの作成
次にMutationを作成します。ブランチは2-create-mutationです。
# import 追加
from typing import List
import strawberry
from fastapi import FastAPI
from strawberry.asgi import GraphQL
# 省略...
# (1) Todoを保存するリストの作成
todos: List[Todo] = []
@strawberry.type
class Query:
# (2): クエリをTodoのリストを返却するように変更
@strawberry.field
def todo(self) -> List[Todo]:
return todos
# (3): Mutationを追加
@strawberry.type
class Mutation:
@strawberry.mutation
def create_todo(self, title: str) -> Todo:
new_todo = Todo(title=title)
todos.append(new_todo)
return new_todo
# (4): スキーマ定義にMutationを追加
schema = strawberry.Schema(query=Query, mutation=Mutation)
graphql_app = GraphQL(schema)
app = FastAPI()
app.add_route('/graphql', graphql_app)
保存リストの作成
# (1) Todoを保存するリストの作成
todos: List[Todo] = []
今回はデータの保存にDBは使わずに、リストに格納していくことにします。 そのためのTodoを保存しておくためのリストの宣言を行っています。
クエリの修正
@strawberry.type
class Query:
# (2): クエリをTodoのリストを返却するように変更
@strawberry.field
def todo(self) -> List[Todo]:
return todos
これまではクエリは決められたただ一つのTodoを返すだけでしたが、ここでは(1)で作成した、Todoのリストを返却するように変更しています。
Mutationの追加
# (3): Mutationを追加
@strawberry.type
class Mutation:
@strawberry.mutation
def create_todo(self, title: str) -> Todo:
new_todo = Todo(title=title)
todos.append(new_todo)
return new_todo
# (4): スキーマ定義にMutationを追加
schema = strawberry.Schema(query=Query, mutation=Mutation)
最後にMutationの追加を行っています。Mutationクラスを作成し、その中に@strawberry.mutationを使用して、Mutationの実際の処理を記述していきます。
create_todoメソッドではTodoのtitleを引数として受け取り、新しいTodoクラスをインスタンス化します。
作成したインスタンスをtodosにappendして、生成したTodoをリストに追加します。
最後に新しく作成したインスタンスを返却して処理は終了です。
作成したMutationwoGraphQLサーバーで実行するためにスキーマ定義にmutationを渡します。
サーバーの起動
先ほど同様に以下コマンドでサーバーを起動します。
uvicorn main:app --reload
localhost:8000にアクセスして、まずはミューテーションを実行します。
mutation{
createTodo(title: "New Todo 1") {
title
}
}
クエリを実行するとミューテーションで作成したTodoが得られます。
query{
todo{
title
}
}
{
"data": {
"todo": [
{
"title": "New Todo 1"
}
]
}
}
3: プロパティの追加
最後にプロパティの追加を行います。現在TodoモデルはStringのtitleというプロパティしか保持していません。これに以下のプロパティを追加します。
- ID : Todoを一意に表すID
- limit(datetime): Todoの期限を保持する
- priority(enum) : Todoの優先順位を保持する
- subTodos : TodoのサブTodo
ブランチは3-add-relationshipです。
まずはmain.pyを以下のようにします。
from typing import List
# import 追加
from datetime import datetime
import uuid
import enum
import strawberry
from fastapi import FastAPI
from strawberry.asgi import GraphQL
# import 追加
from strawberry.scalars import ID
# (1): Priorityで選択可能なenumを定義
@strawberry.enum
class Priority(enum.Enum):
HIGH = "高い"
MEDIUM = "中くらい"
LOW = "低い"
# (2): SubTodo型の定義
@strawberry.type
class SubTodo:
id: ID
subtitle: str
# (3): プロパティの追加
@strawberry.type
class Todo:
id: ID
title: str
limit: datetime
priority: Priority
subTodos: List[SubTodo] = strawberry.field(default_factory=list)
todos: List[Todo] = []
@strawberry.type
class Query:
@strawberry.field
def todo(self) -> List[Todo]:
return todos
@strawberry.type
class Mutation:
# (4): todo作成時に各種プロパティを設定
@strawberry.mutation
def create_todo(self, title: str,
limit: datetime,
priority: Priority
) -> Todo:
new_todo = Todo(
id=strawberry.ID(str(uuid.uuid4())),
title=title,
limit=limit,
priority=priority
)
todos.append(new_todo)
return new_todo
# (5): SubTodoの作成用mutationの定義
@strawberry.mutation
def create_sub_todo(self, todo_id: strawberry.ID, subtitle: str) -> SubTodo:
for todo in todos:
if todo.id == todo_id:
new_sub_todo = SubTodo(id=strawberry.ID(str(uuid.uuid4())), subtitle=subtitle)
todo.subTodos.append(new_sub_todo)
return new_sub_todo
raise ValueError(f"Todo with ID {todo_id} not found")
schema = strawberry.Schema(query=Query, mutation=Mutation)
graphql_app = GraphQL(schema)
app = FastAPI()
app.add_route('/graphql', graphql_app)
Enumの定義
# (1): Priorityで選択可能なenumを定義
@strawberry.enum
class Priority(enum.Enum):
HIGH = "高い"
MEDIUM = "中くらい"
LOW = "低い"
Todoのpriorityフィールドでは、優先順位を高い、中くらい、低い、の中から選べるようにしたいので、enumを使って定義を行います。
SubTodoの定義
# (2): SubTodo型の定義
@strawberry.type
class SubTodo:
id: ID
subtitle: str
Todoでは複数のSubTodoを持てるように変更します。そのためSubTodo型を定義しておきます。
Todoのプロパティの追加
# (3): プロパティの追加
@strawberry.type
class Todo:
id: ID
title: str
limit: datetime
priority: Priority
subTodos: List[SubTodo] = strawberry.field(default_factory=list)
Todoモデルにプロパティの追加を行っています。
# (4): todo作成時に各種プロパティを設定
@strawberry.mutation
def create_todo(self, title: str,
limit: datetime,
priority: Priority
) -> Todo:
new_todo = Todo(
id=strawberry.ID(str(uuid.uuid4())),
title=title,
limit=limit,
priority=priority
)
todos.append(new_todo)
return new_todo
また、create_todoメソッドでも引数を追加し、作成時に渡すように変更します。
SubTodo作成用mutationの作成
# (5): SubTodoの作成用mutationの定義
@strawberry.mutation
def create_sub_todo(self, todo_id: strawberry.ID, subtitle: str) -> SubTodo:
for todo in todos:
if todo.id == todo_id:
new_sub_todo = SubTodo(id=strawberry.ID(str(uuid.uuid4())), subtitle=subtitle)
todo.subTodos.append(new_sub_todo)
return new_sub_todo
raise ValueError(f"Todo with ID {todo_id} not found")
MutationクラスにSubTodoを作成するためのmutationメソッドを追加します。
SubTodoを追加するためのTodoIDを受け取り、IDが一致するTodoに対してさぶTodoを追加します。
もし一致するIDがなければエラーを発生させます。
サーバーの起動
以下コマンドでサーバーを起動します。
uvicorn main:app --reload
localhost:8000にアクセスして、まずはミューテーションを実行します。
mutation {
createTodo(title: "MainTodo", limit: "2024-05-16", priority: MEDIUM) {
id
title
}
}
Mutationの実行結果からIDを取得し、SubTodoを追加します。
mutation {
createSubTodo(todoId: "生成されたID", subtitle: "Sub Todo"){
id
subtitle
}
}
Todoが作成されたか確認するために、クエリを投げます。
query {
todo{
id
title
limit
priority
subTodos {
subtitle
}
}
}
{
"data": {
"todo": [
{
"id": "b58d5881-f39e-4405-b9c1-103bc51b2996",
"title": "MainTodo",
"limit": "2024-05-16T00:00:00",
"priority": "MEDIUM",
"subTodos": [
{
"subtitle": "Sub Todo"
}
]
}
]
}
}
以上でStrawberryを使って、GraphQLサーバを構築する方法の解説を終了します。