Help us understand the problem. What is going on with this article?

FastAPIをMySQLと接続してDockerで管理してみる

この記事では、爆速でAPIを実装したい。また、FastAPIの文献少なすぎて、
MySQLとの連携ってどうやってやるの?と疑問に思っている方に向けて、
三分で環境を構築する方法を紹介しています。
Screenshot from 2019-04-09 03-16-23.png

前提

  • MySQLを触ったことがある。selectやwhereなどがわかる
  • Pythonを少しは触ったことがある。
  • Dockerをインストールして使える状態

※ まだ、Dockerをインストールしていない方は、以下のサイトでわかりやすく紹介しているので、
参考にしながら導入してみましょう!
https://qiita.com/scrummasudar/items/750aa52f4e0e747eed68

FastAPIとは???

Pythonを使ったAPI構築のためのフレームワークです。ちなみに、Python3.6以上で動作します。
Pythonを使ったAPIはFlaskやdjangoなどが有名で、どれも簡単にAPIを構築することができますが、
FastAPIでは、より手軽に、そして動作も速く、非同期通信も簡単に実装することができます。

公式では、NodeJSやGoと同等の高いパフォーマンスとうたっています。

公式にはサンプルも多いので、困ったときはこちらを確認してみるといいでしょう。
https://fastapi.tiangolo.com/

今回やること

  • docker-composeでMySQLとFastAPIを起動する
  • 起動したMySQLに接続し、設定ファイルどおりに構築されているか確認する
  • FastAPIで実装したAPIをSwaggerやcurlで叩いてMySQL上のデータを操作してみる

できたものがこちらになります(お急ぎの方)

今回紹介するコードの完成版はこちらになります
https://github.com/hogeline/sample_fastapi

お急ぎの方はこちらからすぐ環境を構築することができます
はい、三分で実装できましたね笑

お急ぎ出ない方は、以下から本題です。

事前準備

ディレクトリ構造

.
├── code
│   ├── db.py
│   ├── main.py
│   └── model.py
├── docker
│   ├── api
│   │   ├── Dockerfile
│   │   └── requirements.txt
│   └── mysql
│       ├── Dockerfile
│       ├── conf.d
│       │   └── my.cnf
│       └── initdb.d
│           ├── schema.sql
│           └── testdata.sql
└── docker-compose.yml

今回のディレクトリ構造はこのようになっています

codeにはPythonのコードが入ります
dockerにはDockerfileやMySQLの設定、
FastAPIを実行するために必要なパッケージなどの情報が入っています。

1. docker-compose.ymlの作成

docker-compose.yml
version: "3.3"

services:
  # MySQL
  db:
    container_name: "db"
    # path配下のDockerfile読み込み
    build: ./docker/mysql
    # コンテナが落ちたら再起動する
    restart: always
    tty: true
    environment:
      MYSQL_DATABASE: sample_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password # ユーザのパスワード
      MYSQL_ROOT_PASSWORD: password # ルートパスワード
    ports:
      - "3306:3306"
    volumes:
      - ./docker/mysql/initdb.d:/docker-entrypoint-initdb.d # 定義どおりにテーブル作成
      - ./docker/mysql/conf.d:/etc/mysql/conf.d # MySQLの基本設定(文字化け対策)
      - ./log/mysql:/var/log/mysql # ログの保存
    networks:
      - local-net

  # FastAPI
  api:
    # db起動後に立ち上げる
    links:
      - db
    container_name: "api"
    # path配下のDockerfile読み込み
    build: ./docker/api
    ports:
      - "8000:8000"
    volumes:
      - ./code:/usr/src/server
    networks:
      - local-net

# コンテナ間で通信を行うためのネットワークブリッジ
networks:
  local-net:
    driver: bridge

docker-composeでは、MySQLとFastAPIを扱うdbとapiのサービスを作ります
それぞれのコンテナイメージは./docker/mysqlと./docker/apiのDockerfileを参照して作成します

apiからdbのmysqlへ接続するためにコンテナ間の通信が必要だったので、networks: local-netを定義しています

2. Dockerfileを作成する

./docker/mysql/Dockerfile
FROM mysql:5.7

# MySQLの操作ログのファイルを作成
RUN touch /var/log/mysql/mysqld.log
./docker/api/Dockerfile
FROM python:3.7

WORKDIR /usr/src/server
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のバージョンは3.7です
FastAPIを立ち上げるuvicornのオプションに--reloadをつけると
./code/main.pyを編集と同時に変更内容が反映されます

3. pipでインストールするパッケージ一覧

./docker/api/requirements.txt
mysqlclient
sqlalchemy
uvicorn
fastapi

mysqlclientとsqlalchemyはMySQLを操作するために必要で、
FastAPIを起動するために必要なパッケージはuvicornとfastapiのみです

4. MySQL設定ファイルの作成

./docker/mysql/conf.d/my.cnf
[mysqld]
character-set-server=utf8 # mysqlサーバー側が使用する文字コード
skip-character-set-client-handshake # 文字化け対策
default-storage-engine=INNODB # InnoDBを使用
explicit-defaults-for-timestamp=1 # テーブルにTimeStamp型のカラム用
general-log=1 # 実行したクエリの全ての履歴を記録
general-log-file=/var/log/mysql/mysqld.log # ログの出力先

[mysqldump]
default-character-set=utf8

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8 # mysqlのクライアント側が使用する文字コード

sqlalchemyを利用するにあたって、ストレージエンジンはInnoDBじゃないといけません、MySQLではデフォルトでInnoDBを利用しますが、一応指定しましょう

5. スキーマの定義

./docker/mysql/initdb.d/schema.sql
CREATE TABLE user (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(30) NOT NULL,
    age INT,
    PRIMARY KEY (id)
);

スキーマで定義したuserテーブルを作成します

6. テストデータの作成

./docker/mysql/init.db/testdata.sql
INSERT INTO user (name, age) VALUES ("太郎", 15);
INSERT INTO user (name, age) VALUES ("次郎", 18);
INSERT INTO user (name, age) VALUES ("花子", 20);

テーブル作成後、上記のSQLを実行しテストデータを流します

7. PythonでDBと接続するための設定

./code/db.py
# -*- coding: utf-8 -*-
# DBへの接続設定
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session


# 接続したいDBの基本情報を設定
user_name = "user"
password = "password"
host = "db"  # docker-composeで定義したMySQLのサービス名
database_name = "sample_db"

DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
    user_name,
    password,
    host,
    database_name,
)

# DBとの接続
ENGINE = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

# Sessionの作成
session = scoped_session(
    # ORM実行時の設定。自動コミットするか、自動反映するか
    sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=ENGINE
    )
)

# modelで使用する
Base = declarative_base()
# DB接続用のセッションクラス、インスタンスが作成されると接続する
Base.query = session.query_property()

既存のDBを利用したい場合は、このファイルの基本情報を設定している項目を編集することで、
そちらへアクセスすることが可能です

8. FastAPIで扱うモデルを定義する

./code/model.py
# -*- coding: utf-8 -*-
# モデルの定義
from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
from db import Base
from db import ENGINE


# userテーブルのモデルUserTableを定義
class UserTable(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(30), nullable=False)
    age = Column(Integer)


# POSTやPUTのとき受け取るRequest Bodyのモデルを定義
class User(BaseModel):
    id: int
    name: str
    age: int


def main():
    # テーブルが存在しなければ、テーブルを作成
    Base.metadata.create_all(bind=ENGINE)


if __name__ == "__main__":
    main()

ここでは、FastAPIで操作するMySQLのテーブルのモデルの定義と
POSTやPUTで送られてきたリクエストbodyを扱うためにモデルを定義しています

FastAPIの実装

FastAPIの実装を行います
今回は、GETとPOST、PUTを実装しました

./code/main.py
from fastapi import FastAPI
from typing import List  # ネストされたBodyを定義するために必要
from starlette.middleware.cors import CORSMiddleware  # CORSを回避するために必要
from db import session  # DBと接続するためのセッション
from model import UserTable, User  # 今回使うモデルをインポート

app = FastAPI()

# CORSを回避するために設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ----------APIの実装------------
# テーブルにいる全ユーザ情報を取得 GET
@app.get("/users")
def read_users():
    users = session.query(UserTable).all()
    return users

# idにマッチするユーザ情報を取得 GET
@app.get("/users/{user_id}")
def read_user(user_id: int):
    user = session.query(UserTable).\
        filter(UserTable.id == user_id).first()
    return user

# ユーザ情報を登録 POST
@app.post("/user")
# クエリでnameとstrを受け取る
# /user?name="三郎"&age=10
async def create_user(name: str, age: int):
    user = UserTable()
    user.name = name
    user.age = age
    session.add(user)
    session.commit()

# 複数のユーザ情報を更新 PUT
@app.put("/users")
# modelで定義したUserモデルのリクエストbodyをリストに入れた形で受け取る
# users=[{"id": 1, "name": "一郎", "age": 16},{"id": 2, "name": "二郎", "age": 20}]
async def update_users(users: List[User]):
    for new_user in users:
        user = session.query(UserTable).\
            filter(UserTable.id == new_user.id).first()
        user.name = new_user.name
        user.age = new_user.age
        session.commit()

asyncをdefの前につけることで非同期で処理してくれます

defで定義した関数の引数にクエリやリクエストbodyが入ってきます。
例えば、

@app.get("/users/{user_id}")
def read_user(user_id: int):

であれば、"/users/1"のアクセスがあればuser_idは1の値が引数に入ってきます
ここらへんの使い方は、以下のサイトで詳しく紹介しているので参考にしてください
https://qiita.com/mtitg/items/47770e9a562dd150631d

クエリやレスポンスbodyから取得した値を使ってDBを操作します
操作はsqlalchemyを使って行うので、直感的に簡単にできると思います

詳しい使い方は、こちらのサイトで紹介されているので参考にしましょう
https://qiita.com/bokotomo/items/a762b1bc0f192a55eae8

実行手順

さっそく、FastAPIとMySQLを構築してAPIを叩いて見ましょう

1. docker-composeコマンドを実行してみる

コンテナの作成&起動

$ docker-compose up -d --build

コンテナのステータスを確認

$ docker-compose ps
Name              Command               State                 Ports              
---------------------------------------------------------------------------------
api    uvicorn main:app --reload  ...   Up      0.0.0.0:8000->8000/tcp           
db     docker-entrypoint.sh mysqld      Up      0.0.0.0:3306->3306/tcp, 33060/tcp

apiとdbがUPしていることを確認しましょう

2. MySQLへ接続して、設定どおりテーブルが作成されているか確認する

Dockerで作成したMySQLへ接続してみます.
このとき、ホストはlocalhostではなく127.0.0.1であることに注意です

$ mysql -u user -h 127.0.0.1 -D sample_db -p
Enter password:

mysql> show tables;
+---------------------+
| Tables_in_sample_db |
+---------------------+
| user                |
+---------------------+
1 row in set (0.01 sec)

mysql> select * from user;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 太郎   |   15 |
|  2 | 次郎   |   18 |
|  3 | 花子   |   20 |
+----+--------+------+
3 rows in set (0.00 sec)

userテーブルが作成していることが確認できました

3. FastAPIのドキュメントを確認する

FastAPIでは、作成したAPIの仕様を自動でドキュメント化してくれます
ドキュメントはSwaggerというもので作られていて、ドキュメント内でAPIを叩いたりクエリを確認したりできます

http://localhost:8000/docs

上記のリンクへアクセスすると実装したAPIが表示されていると思います
スクリーンショット 2019-04-09 16.23.57.png

4. Swagger上でAPIを叩いてみる

GETで全ユーザ情報を取得

GET /usersを実行してみましょう
Tryを押して
スクリーンショット 2019-04-09 16.32.59.png

Executeを押すと
スクリーンショット 2019-04-09 16.33.22.png

全データが取得できていることが確認できます
スクリーンショット 2019-04-09 16.28.25.png

POSTでユーザ登録

Swaggerでは簡単にPOSTを試すことができます
curlだとヘッダーつけなきゃいけなくて大変ですが...

スクリーンショット 2019-04-09 18.26.55.png

今回はnameに"四郎"、ageに10を入れてPOSTしてみます
クエリとしては/user?name="四郎"&age=10でPOSTします

Excuteを押したあと、200で返ってきたことを確認し
ユーザが登録されているか先程実行したGET /usersを再度実行してみましょう
スクリーンショット 2019-04-09 18.27.34.png

増えていることが確認できました

PUTでユーザの情報を更新

次郎と花子の年齢を変えてみましょう
スクリーンショット 2019-04-09 18.46.00.png

今回、PUTではUserモデルをネストしたリクエストbodyを受け付けているので

[
  {
    "id": 2,
    "name": "次郎",
    "age": 13
  },
  {
    "id": 3,
    "name": "花子",
    "age": 14
  }
]

このようなbody作ってPUTしてみます

200で返ってきてることを確認し、
GET /usersで反映されているか確認してみましょう

スクリーンショット 2019-04-09 18.56.56.png

5. curlでAPIを叩いてみる

$ curl localhost:8000/users/1
{"id":1,"age":15,"name":"太郎"}

curlでuserテーブルのID1の情報が取得できました

感想

FastAPIは簡単にAPIを実装できて非同期処理も簡単にできるので便利だな〜と感じました

少しでも、このサンプルプログラムが役にたったな〜と思った方は
いいね!を押していただければと思います!

リファレンス

DockerComposeを用いたMySQLの環境構築はこちらのサイトを参考にしました

https://qiita.com/Manatee/items/58d0f98a15656ed65136

sqlalchemyはこちらのサイトを参考にしました

https://qiita.com/bokotomo/items/a762b1bc0f192a55eae8

FastAPIの基本的な使い方を日本語でまとめてくれているサイト

https://qiita.com/mtitg/items/47770e9a562dd150631d

公式ドキュメント

https://fastapi.tiangolo.com/features/

satto_sann
触っている技術:Python, React, Ruby, Go, Docker, k8s, SDN(Trema, Faucet) OSS: FastAPI Contributor
zozotech
70億人のファッションを技術の力で変えていく
https://tech.zozo.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした