うわっ・・・FastAPIでGraphQLの構築、楽すぎ・・・？

## 自己紹介
こんにちは，ZOZOテクノロジーズの内定者さっとです。
最近FastAPIをよく利用していますが、今回紹介するGraphQLやWebSocketがサポートされており、
便利すぎでは？と感じる今日この頃です。

※本記事は[ZOZOテクノロジーズ#5](https://qiita.com/advent-calendar/2019/zozo_tech5)の17日目です．
昨日は@e_tyuboさんが「[Git for Windowsでautocrlfの設定を間違えちゃった時の対応](https://qiita.com/e_tyubo/items/ac555ab9d278ac366679)」という記事を出しました．
他にもZOZOテクノロジーズでは5つのアドベントカレンダーを毎日更新しています！

- [ZOZOテクノロジーズ#1](https://qiita.com/advent-calendar/2019/zozo_tech)
- [ZOZOテクノロジーズ#2](https://qiita.com/advent-calendar/2019/zozo_tech2)
- [ZOZOテクノロジーズ#3](https://qiita.com/advent-calendar/2019/zozo_tech3)
- [ZOZOテクノロジーズ#4](https://qiita.com/advent-calendar/2019/zozo_tech4)


## 概要
FastAPIを使ってGraphQLをさくっと構築する方法を紹介したいと思います。

以下、本記事の構成です。

- FastAPI？GraphQLとは?
- FastAPI×GraphQLで楽々ハローワールド
- SQLAlchemyを使ってDB接続

## FastAPIとは
Python3.6以上で動作する高速なAPIフレームワークです。
https://fastapi.tiangolo.com/
2019年初頭頃、少し話題になったと思います。

特徴としては、Node.jsやGoと同等のパフォーマンス性能でかつFlaskライクな書き方で簡単にRESTful APIやGraphQL、WebSocketを構築することができます。ハッカソンなど時間が限られた開発で有効なフレームワークだと思います。

また、Swaggerが自動で生成されるため、動作チェックやドキュメント作成の時間が大幅に短縮されます。

## GraphQLとは
Facebookによって、従来のRESTful APIの問題点を解決するために開発されたオープンソースの言語です。
https://graphql.org/

特徴としては、１つのエンドポイントを使用して、必要なデータを必要な分だけ１回のクエリで取得できるという点です。

この利点によって、従来のRESTful APIで必要なデータを取得するために、余計なデータまで付属してきたという状況を回避することができます。これにより、電力の消費やネットワークの帯域を抑えた通信が可能となりました。

## Hello GraphQL
コードの説明は一旦おいておいて、動かしてみましょう

```bash
$ git clone https://github.com/sattosan/hello_fastapi_graphql.git

# コンテナをビルド＆起動
$ docker-compose up -d --build
```

### GraphQLの動作チェック
コンテナが起動次第、下記のリンクにアクセスしてみましょう
http://127.0.0.1:8000/graphql

そうすると、下記のようなWebユーザインタフェースが表示されると思います。
![Screenshot from 2019-12-14 03-00-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130670/95654222-c6cf-c302-920c-b393e415a717.png)

画像の通り、下記のクエリを左側に記述して、実行すると

```bash
{
  hello(name: "FastAPI")
}
```

右側にレスポンスが表示されることがわかるかと思います。

## コードの説明
では、さっそくコードの中身を見ていきましょう。
### 動作環境
- Ubuntu 18.04.3 および macOS Catalina 10.15.3
- Python 3.8
- FastAPI 0.45.0

### ファイル構成
今回は、Pythonのいろいろなパッケージを使う関係で、Docker Composeを使って環境を構築しています。

```bash
.
├── api
│   └── main.py
├── docker
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt
└── docker-compose.yml

```

### Docker Composeで環境構築
- Composeファイル

```yml:./docker-compose.yml
version: "3"

services:
  # FastAPI
  api:
    container_name: "api"
    build: ./docker/uvicorn
    restart: always
    tty: true
    ports:
      - 8000:8000
    volumes:
      - ./api:/usr/src/api
```

- FastAPIのコンテナを定義

```dockerfile:./docker/uvicorn/Dockerfile
FROM python:3.8

WORKDIR /usr/src/api
ADD requirements.txt ./
# requirements.txtにリストされたパッケージをインストールする
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# FastAPIを8000ポートで待機
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
```

- 必要なPythonのパッケージを一気にインストールするためにリスト化したもの
 
```txt:./docker/uvicorn/requirements.txt 
uvicorn
fastapi
graphene
```

### FastAPIでGraphQLのエンドポイント作成
RESTful APIと違い、GraphQLで扱うエンドポイントは１つだけです。

今回は、FastAPIを使用して、エンドポイント```/graphql```上にGraphQLスキーマを公開するサーバと、クエリを簡単に実行するための**GraphiQL**と呼ばれるWebユーザインタフェースを作成します。

ライブラリ**starlette**を使うことで、これらの機能を実現するためのサポートをしてくれます。

```python:./api/main.py
import graphene
from fastapi import FastAPI
from starlette.graphql import GraphQLApp


# Grapheneを利用したGraphQLスキーマを作成する
class Query(graphene.ObjectType):
    # 引数nameを持つフィールドhelloを作成
    hello = graphene.String(name=graphene.String(default_value="stranger"))

    # フィールドhelloに対するユーザへ返すクエリレスポンスを定義
    def resolve_hello(self, info, name):
        return "Hello " + name


# FastAPIを利用するためのインスタンスを作成
app = FastAPI()
# GraphQLのエンドポイント
app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query)))
```

## SQLAlchemyを使ってDB接続
DBを用意して、GraphQLでデータを取得できるように既存ファイルを編集します。

DBはSQLiteで構築し、ORMモジュールであるSQLAlchemyを使ってデータを操作します。

完成コードはGitHubにあるので、すぐ試したい方は、[こちら](https://github.com/sattosan/sample_fastapi_graphql)にアクセスしてください。


### ファイルの追加&編集
変更後のファイル構成は、このようになっています。

```bash
.
├── api
│   ├── main.py    # 編集
│   ├── models.py  # 追加
│   ├── schema.py  # 追加
│   └── init_data.py # 追加
├── docker
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt # 編集
└── docker-compose.yml

```

#### パッケージの追加
FastAPIのコンテナでDBとデータのやり取りを行うために、
**SQLAlchemy**とGraphQLを扱うための**graphene_sqlalchemy**を追加します。

```txt:./docker/uvicorn/requirements.txt
uvicorn
fastapi
graphene
SQLAlchemy # 追加
graphene_sqlalchemy # 追加
```

#### テーブルモデルの定義
SQLiteと通信するための**db_session**を作成し、
**Department**と**Employee**テーブルを定義します。

```py:./api/models.py
# flask_sqlalchemy/models.py
from sqlalchemy import *
from sqlalchemy.orm import (scoped_session, sessionmaker, relationship,
                            backref)
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))

Base = declarative_base()
# クエリを扱うために宣言
Base.query = db_session.query_property()


class Department(Base):
    __tablename__ = 'department'
    id = Column(Integer, primary_key=True)
    name = Column(String)


class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    hired_on = Column(DateTime, default=func.now())
    department_id = Column(Integer, ForeignKey('department.id'))
    department = relationship(
        Department,
        backref=backref('employees',
                        uselist=True,
                        cascade='delete,all'))
```

#### スキーマーの定義
特定の従業員情報や部署情報を取得するための**Employee**と**Department**を定義します。

また、Queryとして、特定のノードを取得するための**node**と、
すべての従業員情報や部署情報を取得するための、all_employees、all_departmentを提供しています。

```py:./api/schema.py
import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
from models import Department as DepartmentModel, Employee as EmployeeModel


class Department(SQLAlchemyObjectType):
    class Meta:
        model = DepartmentModel
        interfaces = (relay.Node, )


class DepartmentConnections(relay.Connection):
    class Meta:
        node = Department


class Employee(SQLAlchemyObjectType):
    class Meta:
        model = EmployeeModel
        interfaces = (relay.Node, )


class EmployeeConnections(relay.Connection):
    class Meta:
        node = Employee


class Query(graphene.ObjectType):
    node = relay.Node.Field()
    # デフォルトでは、ソートが有効化されている
    all_employees = SQLAlchemyConnectionField(EmployeeConnections)
    # ソートを無効化
    all_departments = SQLAlchemyConnectionField(DepartmentConnections, sort=None)


schema = graphene.Schema(query=Query)

```

#### FastAPIのコードを修正
定義した**スキーマー**及び**クエリ**を**GraphQLApp**に投げて扱えるようにします。
また、 シャットダウン時に、DBセッションを削除するイベントを追加します。

```python:./api/main.py
from fastapi import FastAPI
from starlette.graphql import GraphQLApp

from models import db_session
from schema import schema

# FastAPIのインスタンスを作成
app = FastAPI()

# GraphQLを提供するためのエンドポイントを定義
app.add_route("/graphql", GraphQLApp(schema=schema))

# APIサーバシャットダウン時にDBセッションを削除
@app.on_event("shutdown")
def shutdown_event():
    db_session.remove()
```

### テストデータの挿入
SQLite上で作成したテーブルに下記のコマンドを入力して、テストデータを挿入します。

```bash
$ docker-compose run --rm api python init_data.py
```

**init_data.py**はテストデータをまとめたものです。

```py:./api/init_data.py
# 初期データを流し込むためのスクリプト．以下を実行してTableにデータを流し込む
# docker-compose run --rm api python init_data.py
from models import engine, db_session, Base, Department, Employee
Base.metadata.create_all(bind=engine)

# Tableに流し込む初期データ
engineering = Department(name='Engineering')
db_session.add(engineering)
hr = Department(name='Human Resources')
db_session.add(hr)

peter = Employee(name='Peter', department=engineering)
db_session.add(peter)
roy = Employee(name='Roy', department=engineering)
db_session.add(roy)
tracy = Employee(name='Tracy', department=hr)
db_session.add(tracy)
db_session.commit()
```

#### コンテナの再構築
もろもろ変更したので、コンテナを再構築します。

```bash
$ docker-compose up -d --build
```

### DBのデータをGraphQLでリクエストしてみる
下記のリンクにアクセスして、GraphQLでDBに挿入したデータを取得してみましょう。

http://127.0.0.1:8000/graphql

このようなクエリを投げてみてください。

```graphql
{
  allEmployees {
    edges {
      node {
        id
        name
        department {
          name
        }
      }
    }
  }
}
```

![Screenshot from 2019-12-15 01-34-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130670/6b760879-dc9b-9b0a-8892-780f28becb65.png)

allEmployeesを使って、すべての従業員情報を取得することができました。

## まとめ
今回は、FastAPIを使ってGraphQLの構築方法を紹介しました。
たった数行で構築することができ、とりあえず試してみようと思う方にとっては、
FastAPIはベストな選択だと思います。

明日は、@hmsnakrさんの記事です！お楽しみに！！

## 参考
https://fastapi.tiangolo.com/tutorial/graphql/
https://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/