Skip to content

API ルーター

API ルーターを使えば、 FastAPI アプリのパスごとにファイルを分けることができるので便利。

FastAPIチュートリアル: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter
FastAPIリファレンス: https://fastapi.tiangolo.com/reference/apirouter/


API ルーターを使わない時

例えば下記のような FastAPI アプリを考える。
認証なしでユーザーのCRUDができるアプリのイメージで。(本当ならDBに接続したりすると思うけど省略で。)

# main.py
from fastapi import FastAPI

app = FastAPI()

# このエンドポイントはトップページのようなもの
@app.get("/")
def read_root():
    return {"Hello": "World"}

# 以下のエンドポイントはユーザーのCRUDを行うためのもの
@app.post("/users/{user_id}")
def create_user(user_id: int):
    return {"user_id": user_id}


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


@app.patch("/users/{user_id}")
def update_user(user_id: int):
    return {"user_id": user_id}


@app.delete("/users/{user_id}")
def delete_user(user_id: int):
    return {"user_id": user_id}

やっぱり、ユーザー関連のエンドポイントは認証必須にしたいと思ったら下記のように変更しないと。

この認証方法は脆弱すぎ

脆弱な認証方式なので本番では使えない・・・こんな感じっていうイメージだけ掴めれば良くて、 API ルーターが役立つ部分にフォーカスしてるので。

# main.py
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

ADMIN_USER = "admin@example.com"
ADMIN_PASSWORD = "secret_password"


def verify_admin(username: str = "", password: str = ""):
    if username != ADMIN_USER or password != ADMIN_PASSWORD:
        raise HTTPException(
            status_code=401,
            detail="Incorrect username or password",
        )
    return username


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.post("/users/{user_id}")
def create_user(
    user_id: int,
    admin: str = Depends(verify_admin),
):
    return {"user_id": user_id}


@app.get("/users/{user_id}")
def read_user(
    user_id: int,
    admin: str = Depends(verify_admin)
):
    return {"user_id": user_id}


@app.patch("/users/{user_id}")
def update_user(
    user_id: int,
    admin: str = Depends(verify_admin)
):
    return {"user_id": user_id}


@app.delete("/users/{user_id}")
def delete_user(
    user_id: int,
    admin: str = Depends(verify_admin)
):
    return {"user_id": user_id}

Note

Depends がいきなり出てくるけど、イメージとしてはエンドポイントにある関数の引数に Depends を書いておけば、 関数を実行する前に"引数をもらうための関数"が実行されたりする。これをうまいこと使ったら認証を実装できる。

一気にゴチャゴチャしてしまう・・・ユーザー関連のエンドポイント全てに Depends(verify_admin) を追加しないといけないし、機能的にも read_root だけ明らかに浮いてるし。。。


API ルーターを使った時

API ルーターを使って整理する。

# main.py
from fastapi import FastAPI

from users import router as users_router

app = FastAPI()

app.include_router(users_router)


@app.get("/")
def read_root():
    return {"Hello": "World"}

# users.py
from fastapi import APIRouter, Depends, HTTPException

ADMIN_USER = "admin@example.com"
ADMIN_PASSWORD = "secret_password"


def verify_admin(username: str = "", password: str = ""):
    if username != ADMIN_USER or password != ADMIN_PASSWORD:
        raise HTTPException(
            status_code=401,
            detail="Incorrect username or password",
        )
    return username


router = APIRouter(prefix="/users", tags=["users"], dependencies=[Depends(verify_admin)])


@router.post("/{user_id}")
def create_user(user_id: int):
    return {"user_id": user_id}


@router.get("/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}


@router.patch("/{user_id}")
def update_user(user_id: int):
    return {"user_id": user_id}


@router.delete("/{user_id}")
def delete_user(user_id: int):
    return {"user_id": user_id}

機能ごとにファイルが分かれたおかげで、 Depends(verify_admin) 1つでユーザー関連のエンドポイントを全て認証必須にできる。
あと、 verify_admin 関数もユーザー関連のエンドポイントにしか必要じゃないので main.py が本来あるべき姿になってる感じがする。
もし、アイテム関連のエンドポイントを追加したいとなった時でも、 items.py を作って main.pyinclude_router しておけばOK。