はじめに
FastAPIをある程度触ったので、次はFlaskを触ってみた。
PythonのWebフレームワークを調べると必ず名前が出てくるのがFlask。「マイクロフレームワーク」という言葉が気になっていたので、FastAPIとどう違うのかを中心に整理した。
インストールと最初のアプリ
pip install flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, World!"
@app.route("/users/<int:user_id>")
def get_user(user_id):
return {"user_id": user_id, "name": "田中"}
flask run
# または
python app.py
デコレータでルートを定義する書き方はFastAPIとそっくり。ただしFastAPIが@app.get()と HTTPメソッドまで指定するのに対して、Flaskは@app.route()でパスだけ定義するのがデフォルト。
# Flaskでメソッドを指定する場合
@app.route("/users", methods=["GET", "POST"])
def users():
...
FastAPIと並べて比較する
同じAPIをFastAPIとFlaskで書き比べてみた。
ルーティング
# FastAPI
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
# Flask
from flask import Flask
app = Flask(__name__)
@app.route("/users/<int:user_id>")
def get_user(user_id):
return {"user_id": user_id}
パスパラメータの書き方が違う。Flaskは<int:user_id>という独自の構文、FastAPIは{user_id}でPythonの型ヒントと組み合わせる。
JSONレスポンス
# FastAPI → dictを返すだけで自動でJSONになる
@app.get("/users")
def list_users():
return [{"id": 1, "name": "田中"}]
# Flask → jsonifyが必要(Flask 2.2以降はdictも自動変換)
from flask import jsonify
@app.route("/users")
def list_users():
return jsonify([{"id": 1, "name": "田中"}])
# Flask 2.2以降はこれでも動く
@app.route("/users")
def list_users():
return [{"id": 1, "name": "田中"}]
リクエストボディの取得
# FastAPI → Pydanticモデルで型安全に受け取る
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
@app.post("/users")
def create_user(user: UserCreate):
return {"name": user.name}
# Flask → requestオブジェクトから手動で取り出す
from flask import request
@app.route("/users", methods=["POST"])
def create_user():
data = request.get_json()
name = data.get("name")
email = data.get("email")
return {"name": name}
ここが一番大きな違い。FastAPIはPydanticによるバリデーションが標準で入っているが、Flaskはrequest.get_json()で辞書を取り出して自分で処理する必要がある。バリデーションが必要ならmarshmallowやflask-pydanticなどを別途入れる。
Flaskのrequestオブジェクト
Flaskはリクエストの情報をrequestグローバルオブジェクトから取り出す。
from flask import request
@app.route("/search")
def search():
# クエリパラメータ
keyword = request.args.get("keyword", "")
page = request.args.get("page", 1, type=int)
# リクエストボディ(JSON)
data = request.get_json()
# フォームデータ
name = request.form.get("name")
# ヘッダー
token = request.headers.get("Authorization")
# メソッド
print(request.method) # GET / POST など
return {"keyword": keyword, "page": page}
FastAPIは引数に型ヒントを書けば自動で解決されるが、Flaskはrequestオブジェクトを自分で操作する必要がある。PHPの$_GET/$_POST/$_SERVERを自分で触る感覚に近い。
レスポンスのカスタマイズ
from flask import jsonify, make_response
# ステータスコードを指定
@app.route("/users", methods=["POST"])
def create_user():
return jsonify({"message": "作成しました"}), 201
# ヘッダーを追加
@app.route("/data")
def get_data():
response = make_response(jsonify({"data": "..."}))
response.headers["X-Custom-Header"] = "value"
return response
FastAPIのstatus_code=201はデコレータで指定するが、Flaskはタプルで返す。
エラーハンドリング
from flask import jsonify
@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "見つかりません"}), 404
@app.errorhandler(500)
def server_error(e):
return jsonify({"error": "サーバーエラー"}), 500
# 独自例外
class DomainError(Exception):
def __init__(self, message: str, code: int = 400):
self.message = message
self.code = code
@app.errorhandler(DomainError)
def handle_domain_error(e):
return jsonify({"error": e.message}), e.code
@app.route("/items/<int:item_id>")
def get_item(item_id):
if item_id < 0:
raise DomainError("IDは0以上を指定してください")
return {"item_id": item_id}
@app.errorhandler()デコレータでエラーハンドラーを登録するのはFastAPIの@app.exception_handler()とほぼ同じ感覚。
Blueprintでルートを分割する
アプリが大きくなったらBlueprintでファイルを分割する。FastAPIのAPIRouterに相当する。
myapp/
├── app.py
└── blueprints/
├── __init__.py
├── users.py
└── items.py
# blueprints/users.py
from flask import Blueprint
users_bp = Blueprint("users", __name__, url_prefix="/users")
@users_bp.route("/")
def list_users():
return [{"id": 1, "name": "田中"}]
@users_bp.route("/<int:user_id>")
def get_user(user_id):
return {"user_id": user_id}
# app.py
from flask import Flask
from blueprints.users import users_bp
from blueprints.items import items_bp
app = Flask(__name__)
app.register_blueprint(users_bp)
app.register_blueprint(items_bp)
FastAPIのinclude_router()に相当するのがregister_blueprint()。url_prefixでプレフィックスを設定するのも同じ。
Flask-SQLAlchemy — ORMの組み込み
FlaskにはデフォルトでORMがないのでFlask-SQLAlchemyを使うのが一般的。
pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///mydb.db"
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
# テーブル作成
with app.app_context():
db.create_all()
@app.route("/users")
def list_users():
users = User.query.all()
return [{"id": u.id, "name": u.name} for u in users]
PHPのEloquentに近い書き方。ただしFastAPIでよく使うSQLAlchemyの書き方とは少し異なる(Flask-SQLAlchemyはFlaskのapp contextと統合されている)。
Flaskのテスト
import pytest
from app import app
@pytest.fixture
def client():
app.config["TESTING"] = True
with app.test_client() as client:
yield client
def test_index(client):
response = client.get("/")
assert response.status_code == 200
def test_create_user(client):
response = client.post(
"/users",
json={"name": "田中", "email": "tanaka@example.com"},
)
assert response.status_code == 201
assert response.get_json()["name"] == "田中"
app.test_client()でテスト用のHTTPクライアントが作れる。FastAPIのTestClient(starlette)と同じ感覚で使える。
FastAPIとFlaskの比較まとめ
| 項目 | Flask | FastAPI |
|---|---|---|
| リリース | 2010年 | 2018年 |
| 型ヒント活用 | オプション | 中心設計 |
| バリデーション | 別途ライブラリが必要 | Pydantic標準 |
| ドキュメント自動生成 | 別途設定が必要 |
/docsで自動 |
| 非同期サポート | Flask 2.0以降(限定的) | ネイティブ対応 |
| リクエスト取得 |
requestグローバル |
引数で受け取る |
| ORM | Flask-SQLAlchemy | SQLAlchemy(直接) |
| 学習コスト | 低い | やや高い |
| エコシステム | 成熟・拡張が豊富 | 成長中 |
どちらを選ぶか
触ってみた感想として:
Flaskが向いていると思う場面
- シンプルなWebアプリ・管理画面(HTMLテンプレート含む)
- 既存のFlaskプロジェクトのメンテナンス
- 学習コストを低くしたいとき
FastAPIが向いていると思う場面
- 型安全なAPIを最初から作りたいとき
- Swagger UIの自動生成が必要なとき
- 非同期処理が必要なとき
Flaskは「シンプルな分だけ自由」という設計で、必要なものを自分で選んで組み合わせる。FastAPIは「型ヒントを書けば多くのことが自動化される」という設計。どちらが優れているというより、用途と好みで選ぶ感じ。
自分は新規のAPI開発はFastAPIで書いて、Flaskは既存コードを読むときや軽いスクリプトに使う程度にしようと思っている。