爆速PythonフレームワークFastAPIをMySQLに繋いでRESTfulなAPIを作成してみた。

# FastAPI とは？
![68747470733a2f2f666173746170692e7469616e676f6c6f2e636f6d2f696d672f6c6f676f2d6d617267696e2f6c6f676f2d7465616c2e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650287/f610135b-c1e5-0050-a9b4-e2e5f0c0b326.png)

FastAPIとはpython3.6以上を対象とした、APIを作成するためのモダンで処理速度が爆速なフレームワークです。

主な特徴としては

- **速さ**: 非常にハイパフォーマンス。NodeJSやGoに匹敵する処理速度を誇る(StarletteとPydanticのおかげもあり)。Pythonの数あるフレームワークのうち最も処理速度が速いフレームワークの一つ。

- **コードの簡素化**: コードの書く速度を約2~3倍から上昇させる。(*)

- **バグの少なさ**: 約40%ほどの人為的コードバグを減らすことが可能。(*)

- **直感的に書ける**: エディターのサポートも充実、補完も効きます。デバッグにかかる時間を減らすことが可能。

- **簡単**: 簡単に書け、理解しやすいように設計されている。ドキュメント読むのに時間がたくさんかかる心配もない。

- **短い**: コードの重複を避けることができる。渡す引数を変えるだけで様々な機能を提供する関数を備えている。

- **堅実**: 本番環境でも開発環境と差異のないコードを使える。

- **Swaggerの提供**: 作成したAPIはデフォルトで備えているSwaggerをもとに自動でドキュメント化され、各処理を実行できる。


(*)FastAPI制作チーム調べ、だそうです。

#とりあえずサーバーを立ち上げてみる

「コードは言葉より物を言う」と言うことで、早速使っていきたいと思います。

まずは適当にフォルダを作り

```
mkdir fastapi-practice
```

必要なパッケージをインストールします。

```
pip install fastapi sqlalchemy uvicorn　mysqlclient
```
グローバルインストールが嫌な方はpoetryなどを使ってインストールしてください(このあとでどのみちpoetry使います)。

FastAPIを動かすために必要な以下のファイルを作成します

```
touch main.py
```

そして以下の様にコードを記述していきます。

```python:main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def index():
    return {'Hello': 'World'}
```



なんとこれだけでサーバーが立ち上がってしまいます。

```
uvicorn main:app
```

と打つだけでサーバーが立ち上がったはずです。
http://localhost:8000/
をブラウザで表示してみると`{"Hello":"World"}`と表示されているはずです。

爆速ですね、FastAPI。
しかもSwaggerによりAPIの仕様も自動で作成されています！！（驚き！）
http://localhost:8000/docs
を表示してみてください。お洒落なUIで仕様書が作られているはずです。

タブを開いて`Try it out`のボタンを押すと実際にリクエストを送り、レスポンスを確認することだってできてしまいます！(感動!!)


![スクリーンショット 2020-09-24 14.05.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650287/ecc6631d-9207-58be-271e-acf8e1b28f27.png)

ちなみに
http://localhost:8000/redoc
も自動で作られておりさらに詳細なドキュメントを簡単に作ることもできてしまいます!(凄すぎ!!!)




# 実際に使ってみる (docker環境構築編)
それではリアルケースを想定したFastAPI + MySQLでRESTfulなAPIを作成してみましょう。

FastAPIはdockerで簡単に環境構築できるのでmysqlとFastAPIをそれぞれ今時っぽくコンテナ内で動かし、通信させる様にしてみます。


まずはフォルダ内にdocker-compose.ymlとdocker-sync.yml, Dockerfileを作成します。

```
touch docker-compose.yml docker-sync.yml Dockerfile
```

dockerの詳しい使い方の説明はここでは省きますが、Dockerfileにコンテナを作成するための情報、docker-compose.ymlに作成されたコンテナ上で走らせるコマンド、docker-sync.ymlでローカルの開発環境とdockerコンテナ内のファイルをリアルタイムで同期するためにコードをつらつらと書いていきます。

docker-syncの使い方は他の方が書いてくださった以下の様な記事が参考になるかと思うので読んでみてください。
docker-syncは使わなくてもできますが、同期速度を爆速!にするために私は使ってます。

https://qiita.com/Satoshi_Numasawa/items/278a143aa41735e1b0da


それではDockerfileからコードを書いていきます。

```docker:Dockerfile
FROM python:3.8-alpine
RUN apk --update-cache add python3-dev mariadb-dev gcc make build-base libffi-dev libressl-dev
WORKDIR /app
RUN pip install poetry
```

パッケージ管理にはpoetryを使います。
パッケージ管理にはpipenvやpyflowなどもあるのでここは好みですかね...?

https://qiita.com/sk217/items/43c994640f4843a18dbe
こちらの記事に各パッケージマネージャーが分かりやすくまとめられています。
気になる方は是非一読してみてください。


続いてdocker-sync.yml

```docker:docker-sync.yml
version: "2"
options:
  verbose: true
syncs:
  fastapi-practice-sync:
    src: "."
    notify_terminal: true
    sync_strategy: "native_osx"
    sync_userid: "1000"
    sync_excludes: [".git", ".gitignore", ".venv"]
```

そしてdocker-compose.ymlです

```docker:docker-compose.yml
version: "3"
services:
  db:
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_DATABASE: fastapi_practice_development
      MYSQL_USER: root
      MYSQL_PASSWORD: "password"
      MYSQL_ROOT_PASSWORD: "password"
    ports:
      - "3306:4306"
    volumes:
      - mysql_data:/var/lib/mysql
  fastapi:
    build:
      context: .
      dockerfile: "./Dockerfile"
    command: sh -c "poetry install && poetry run uvicorn main:app --reload --host 0.0.0.0 --port 8000"
    ports:
      - "8000:8000"
    depends_on:
      - db
    volumes:
      - fastapi-sync:/app:nocopy
      - poetry_data:/root/.cache/pypoetry/

volumes:
  mysql_data:
  poetry_data:
  fastapi-sync:
    external: true
```
docker-compose.ymlでミソなのが永続化するデータとその場かぎりのデータをうまく使い分けることです。
永続化しないデータはdocker-compose downするたびにリセットされてしまいます。

今回のケースだとmysql内のデータ、poetryでインストールしたパッケージは永続化させ、コンテナを立ち上げる度にmysql内のデータが空になったりパッケージをダウンロードしなくていい様にします。
また書いていくコードはdocker-syncを使って同期させたいので`fastapi-practice-sync:/app:nocopy`を記述して勝手に同期されるのを防ぎます。

MySQLもdockerから最新のイメージをpullして構築していきます。

ここまででdockerのセットアップは終了です。

#実際に使ってみる(FastAPI設定編)

まずはFastAPIに必要なパッケージをインストールするpoetryのセットアップです。

```
poetry init
```
をターミナルで叩きます。
そうすると対話形式でセットアップが始まりますのでyesかnoを連打しましょう。(基本デフォルトの設定で問題ないのでEnter連打でも問題ない...と思います)

そうすると`pyproject.toml`というファイルが作成されたかと思います。

ここにパッケージの依存情報が追記されていくのでpoetryを使ってFastAPIを立ち上げるのに必要なパッケージをインストールしていきましょう。

```
poetry add fastapi sqlalchemy uvicorn　mysqlclient
```

こちらを入力しパッケージのインストールが終わるのを待ちます。
終了したら、`pyproject.toml`を開いてみるとインストールされたパッケージの情報が記載されていることがわかります。

```toml:pyproject.toml

[tool.poetry]
name = "fastapi-practice"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.8"
fastapi = "^0.61.1"
sqlalchemy = "^1.3.19"
uvicorn = "^0.11.8"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
```

そうしたらあとは`docker-compose build`を打ち、イメージをビルドして`docker-sync-stack start`を入力するだけです！

*`docker-sync-stack start`は`docker-sync start`と`docker-compose up`を同時に実行するコマンドです。ログもよしなに出してくれるので便利です。

新たにパッケージをインストールする際は、まずはローカルで`poetry add`でパッケージをインストールしてdockerコンテナを再起動させればコンテナ内にも同期されるはずです！

# マイグレーションをする

お次はDB（MySQL）と連携させていきます。

今回はCRUDの勉強といえばTodoリストの作成！なのでTodoテーブルを定義しマイグレーションをかけていくことにします。

### Todos Table
|column  |datatype 
|---|---|---|
|id |integer 
|title |string 
|content |string  
|done |boolean  

この様な構成のテーブルをマイグレーションしていきます。

まずはデータベースを定義するファイルを作り

```
touch db.py
```

以下の内容を書き込んでいきます。

```python:db.py

from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker, scoped_session

user_name = "root"
password = "password"
host = "db"
database_name = "fastapi_practice_development"

DATABASE = f'mysql://{user_name}:{password}@{host}/{database_name}'

engine = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

Base = declarative_base()

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


class Todo(Base):
    __tablename__ = 'todos'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(30), nullable=False)
    content = Column(String(300), nullable=False)
    done = Column(Boolean, default=False)


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


def main():
    Base.metadata.drop_all(bind=engine)
    Base.metadata.create_all(bind=engine)


if __name__ == "__main__":
    main()
```

FastAPIはsqlalchemyというPythonの中で最もよく利用されるORM(Object-Relation Mapping)の一つを使ってデータベースとPythonのオブジェクトを関連付けるのが主流みたいです。

これを書いたらdockerのコンテナ内に入り、マイグレーションをかけていきます。


```
docker-sync-stack start
```

でコンテナを立ち上げ同期モードにし、

```
docker container ls
```

で立ち上がっているコンテナリストをみます。
そしたら、

```
docker exec -it {コンテナ名} sh
```

を叩き、コンテナの中に入ります。
そして、以下のコマンドでマイグレーションをかけていきます。

```
poetry run python db.py
```

そうすると無事マイグレーションが走りテーブル作成に成功したのではないでしょうか！


そしたら次はCRUD処理をFastAPIで書いていきます。

# FastAPIでCRUD処理を書こう

拡張性を意識してFastAPIに内蔵されているinclude_routerという機能を使い、ファイルを分割していきます。

```
mkdir routers
```

と打ち、

```
touch routers/todo.py
```
というファイルを作ります。
こちらにCRUD処理を書いていきます。

```python:routers/todo.py
from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from starlette.requests import Request
from pydantic import BaseModel
from db import Todo, engine, get_db

router = APIRouter()


class TodoCreate(BaseModel):
    title: str
    content: str
    done: bool


class TodoUpdate(BaseModel):
    title: str
    content: str
    done: bool


@router.get("/")
def read_todos(db: Session = Depends(get_db)):
    todos = db.query(Todo).all()
    return todos


@router.get("/{todo_id}")
def read_todo_by_todo_id(todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(Todo).filter(Todo.id == todo_id).first()
    return todo


@router.post("/")
def create_todo(todo: TodoCreate,  db: Session = Depends(get_db)):
    db_todo = Todo(title=todo.title,
                   content=todo.content, done=todo.done)
    db.add(db_todo)
    db.commit()


@router.put("/{todo_id}")
def update_todo(todo_id: int, todo: TodoUpdate, db: Session = Depends(get_db)):
    db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
    db_todo.title = todo.title
    db_todo.content = todo.content
    db_todo.done = todo.done
    db.commit()


@router.delete("/{todo_id}")
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
    db_todo = db.query(Todo).filter(Todo.id == todo_id).first()
    db.delete(db_todo)
    db.commit()
```

この様にしてざっとCRUD操作を書いていきました。
@routerの後にリクエスト名を書き、動作対象のURLを書くだけです。

そしたらこちらを読み込める様に`main.py`も編集していきます。

```python:main.py
from fastapi import FastAPI
from routers import todos

app = FastAPI()


app.include_router(
    todos.router,
    prefix="/todos",
    tags=["todos"],
    responses={404: {"description": "Not found"}},
)
```

prefixはurlのパスを作ってくれます。tagsはdocsを見やすい様グルーピング化してくれます。

そうして
http://localhost:8000/docs
に接続すると以下の様になっているはずです！


![スクリーンショット 2020-09-24 14.53.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650287/17f587b8-1264-7717-8c40-90737aa4f6b3.png)


タブを開いてポチポチボタンを押してCRUD処理を試してみてください！

このままだとフロントエンドから呼ぶ際CORSエラーが起きるので別アプリから呼ぶ際は以下のCORSの処理を追記してみてください。

```python:main.py
# 追記
from starlette.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
```


#まとめ

FastAPIいかがだったでしょうか？
こんなにも少ないコード量でAPIが作成できてしまうのがとても魅力的ですね。
Pythonでマイクロサービスを作成する際とても相性が良さそうです。

Qiita初投稿だったため何か分かりづらい点あったら質問くださいませ！
これからはなるべくQiitaにもアウトプットしていきたい...です（頑張る