Fast APIでDockerのMySQLコンテナに接続する方法

# はじめに
初めてやったときに若干接続するまでに手こずったので備忘録として書き残しておきます。
ディレクトリ構造は最終的に以下のようになります。
```
dbtest
├─ docker-compose.yml
├─ requirements.txt
│
└─blog
    ├─ database.py
    ├─ main.py
    ├─ models.py
    ├─ schemas.py
    ├─ settings.py
    └─ __init__.py
```

# インストールするパッケージ
requirements.txtに以下の内容を記述。
```text:requirements.txt
mysqlclient
sqlalchemy
uvicorn
fastapi
```
- mysqlclient
    - mysqlをDBに使うときに必要
- sqlalchemy
    - モデルとデータベースを紐づけ
- uvicorn
    - サーバの起動に必要
- fastapi
    - fastapiの利用に必要

以下のコマンドでインストール
```terminal
pip install -r requirements.txt
```

# 接続するURL
DB接続設定で使うURLは以下のようなものになります。

```py
# データベース作成時に設定したやつ
DB_USER = "root"
DB_PASS = "pass"
DB_NAME = "sample_db"

# 接続するホストのIPアドレス
DB_HOST = "127.0.0.1"
# 接続するポート番号
DB_PORT = "3007"

# mysql://root:pass@127.0.0.1:3007/sample_db
DATABASE_URL = f"mysql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
```

database設定を書き込むpythonファイルの全体像は以下のようになりました。
settings.pyに接続に必要な値は分離しています。

```py:database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from settings import DB_USER, DB_PASS, DB_NAME, DB_PORT, DB_HOST

DATABASE_URL = f"mysql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"

engine = create_engine(DATABASE_URL)

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

Base = declarative_base()
```

# モデルの作成
まず、schemas.pyでスキーマを定義。
```py:schemas.py
from pydantic import BaseModel

class Blog(BaseModel):
    title: str
    body: str
```

次に、モデルを記述するためのファイルを作成。ここで、String型は文字列の最大長をString(16)みたいな感じで指定しないとエラーを吐かれたので注意です。

```py:models.py
from sqlalchemy import Column, Integer, String
from database import Base

class Blog(Base):
    __tablename__ = "blogs" # テーブルの名前を明示できる

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(16))
    body = Column(String(16))
```

モジュールをmain.pyで統合する。

```py:main.py
from fastapi import FastAPI, Depends
from schemas import Blog
from models import Base
from database import engine, sessionLocal
from sqlalchemy.orm import Session
import models

app = FastAPI()

#DBテーブル作成
Base.metadata.create_all(bind=engine)

def get_db():
    db = sessionLocal()

    try:
        yield db
    finally:
        db.close()

@app.post('/blog')
def create(blog: Blog, db: Session = Depends(get_db)):
    new_blog = models.Blog(title=blog.title, body=blog.body)
    db.add(new_blog)
    db.commit()
    db.refresh(new_blog)
    return new_blog
```

# MySQLコンテナを起動
docker-compose.ymlに以下の内容を記述。
```dockerfile
version: '3'

services:
  db:
    image: mysql:5.7
    container_name: testMysql
    ports:
      - "3307:3306"
    environment:
      MYSQL_DATABASE: sample_db
      MYSQL_ROOT_USER: root
      MYSQL_ROOT_PASSWORD: pass
```

docker-compose.ymlが直下にあるディレクトリに移動して、コンテナを立ち上げる。

```console
docker-compose up -d
```

# サーバーを起動
blogディレクトリに移動して、サーバーを立ち上げる。
```console
uvicorn main:app --reload
```

mysql workbenchで確認してみると...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742387/12d73b10-95fa-de8e-c639-ed5bd8dedb7f.png" width=500>

blogsテーブルが自動生成されていることが確認できました。

ちなみに、Dockerコンテナから直接確認するには以下のようにします。
```console
docker exec -it testMysql /bin/bash
mysql -uroot -p
//ここでパスワード求められるので、設定したものを入力
use sample_db
SHOW TABLES; //下のような図が確認できたらOK
+---------------------+
| Tables_in_sample_db |
+---------------------+
| blogs               |
+---------------------+
```

# データを送信してみる
サーバーを立ち上げた状態で以下のURLにアクセス。
`http://localhost:8000/docs`

次のようなページが開くので、「Try it out」をクリック。
![スクリーンショット 2022-11-07 153322.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742387/48919d82-2d9d-2b1b-bb57-11a6ff272ab0.png)
適当な値を入力してexecute
![スクリーンショット 2022-11-07 153537.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742387/e64ad6d7-6c0e-65ab-826d-24f69874eb65.png)
mysqlコンテナで以下のクエリを実行してpostしたデータが登録されていたら成功。
```sql
SELECT * FROM blogs;
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742387/8ae58ede-11a0-00a9-7cba-3c0663f0fe9a.png" width=500>

# おわりに
Fast APIはかなり簡潔でわかりやすいなあと思いました。初心者の人はFast APIを最初に触ってから他のフレームワークに移動するって流れにしたら理解しやすくなるのでは？と感じています。