FastAPIとStrawberryでGraphQL超入門(QueryとMutation)
はじめに
本記事では、FastAPI+Strawberryで単純なQueryとMutationを実装し呼び出すことを目標にします。
GraphQLとは
GraphQLとはAPI向けのクエリ言語のことです。
主な特徴は以下の通りです。
- エンドポイントが1つだけ
 - 複数のリソースを取得できる
 - スキーマによる型付けができる
 - Query、Mutation、Subscriptionがある
 
きちんとした説明はこちらの記事を読むとよさそう。
Strawberryの準備
今回はPythonのFastAPI+Strawberryで実装します。
必要なライブラリをインストールしておきます。
$ pip install fastapi
$ pip install uvicorn
$ pip install strawberry-graphql[fastapi]
公式ドキュメントのコードをコピペしてHello Worldしてみましょう。
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class Query:
    @strawberry.field
    def hello(self) -> str:
        return "Hello World"
schema = strawberry.Schema(Query)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
9000番ポートで起動します
$ uvicorn main:app --reload --host 0.0.0.0 --port 9000
localhost:9000/graphqlにアクセスすると下の画像のようなテスターが出てきます。

ここで実装したQueryやMutationのレスポンスを確認することができます。
コピペしたコードにはhelloというQueryがあるので、テスター側で
query {
  hello
}
と書いてやることで
{
  "data": {
    "hello": "Hello World"
  }
}
というレスポンスが得られます。

これでStrawberryの準備とHello Worldが終わりました🙌
Queryの実装
Queryを実装します。QueryはRESTでいうところのGETにあたります。
Hello World時にすでにQueryは実装されていましたが、Queryは以下のように実装します。
@strawberry.type
class Query:
    @strawberry.field
    def hello(self) -> str:
        return "Hello World"
@strawberry.typeでデコレートされたQueryクラスのなかで@strawberry.fieldでデコレートされたメソッドを書くことでQueryが実装できます。(メソッド戻り値の型指定を忘れずに!)
では、新しくQueryを追加してみましょう。
 @strawberry.type
 class Query:
     @strawberry.field
     def hello(self) -> str:
         return "Hello World"
    
+   @strawberry.field
+   def bye(self) -> str:
+       return "Bye!"
これでbyeクエリの実装ができました。
Hello Worldのときと同じようにテスター上で
query {
  bye
}
とすることで
{
  "data": {
    "bye": "Bye!"
  }
}
というレスポンスが得られます。
また、Queryは複数個同時に呼ぶことができます。
例えば
query {
  hello,
  bye
}
上記のクエリは
{
  "data": {
    "hello": "Hello World",
    "bye": "Bye!"
  }
}
というレスポンスを返します。
クエリの戻り値にJSONを指定したいとき
from strawberry.scalars import JSON
strawberryのJSON型が存在しているので、こいつを使えばJSONを戻り値に指定できます。
Mutationの実装
Mutationを実装しましょう。RESTではPOST、PATCH、DELETEにあたるようです。
Mutationは以下のように実装できます。
+@strawberry.type
+class Mutation:
+   @strawberry.mutation
+   def thousand(self, number:int) -> int:
+       return number * 1000
-schema = strawberry.Schema(Query)
+schema = strawberry.Schema(Query, Mutation)
 graphql_app = GraphQLRouter(schema)
 app = FastAPI()
 app.include_router(graphql_app, prefix="/graphql")
Mutationクラスを追加し、その中で@strawberry.mutationでデコレートされたメソッドを定義しましょう。
もうひとつメソッドを追加してみます。
 @strawberry.type
 class Mutation:
    @strawberry.mutation
    def thousand(self, number:int) -> int:
        return number * 1000
+   @strawberry.mutation
+   def hundred(self, number:int) -> int:
+       return number * 100
 schema = strawberry.Schema(Query, Mutation)
 graphql_app = GraphQLRouter(schema)
 app = FastAPI()
 app.include_router(graphql_app, prefix="/graphql")
MutationもQueryと同じように1つでも複数でも呼び出すことができます。
# 1つの場合
mutation{
  thousand(number:1)
}
# 複数の場合
mutation{
  thousand(number:1),
  hundred(number:1)
}
//  1つの場合のレスポンス
{
  "data": {
    "thousand": 1000
  }
}
//  複数の場合のレスポンス
{
  "data": {
    "thousand": 1000,
    "hundred": 100
  }
}
APIの叩き方
いままではテスターからQueryやMutationを呼び出していましたが、今度は普通にAPIを叩いてみましょう。
FastAPIを使っているので、http://localhost:9000/docsにアクセスするとSwagger UIにアクセスできます

/graphqlエンドポイントはGETとPOSTを受け付けていることがわかります。
GETでの叩き方
GETメソッドで呼び出す場合は、クエリパラメータを付けることで呼び出すことができます。
- Query
http://localhost:9000/graphql?query=query{hello}にGETでアクセスします。
レスポンスは 
{
    "data": {
        "hello": "Hello World"
    }
}
となり、正常に呼び出せていることがわかります。

- Mutation
GETメソッドでのMutationの呼び出しは許可されていません。 
POSTでの叩き方
http://localhost:9000/graphqlにPOSTでアクセスします。
- Query
 
// リクエストボディ
{
  "query":"query{hello}"
}
// レスポンスボディ
{
    "data": {
        "hello": "Hello World"
    }
}

- Mutation
 
// リクエストボディ
{
  "query":"mutation{thousand(number:1)}"
}
// レスポンスボディ
{
    "data": {
        "thousand": 1000
    }
}

これで、GETとPOSTのどちらでもAPIを叩くことができました!
Discussion