\ お問い合わせはこちら! /

【FastAPI】API ルーティングの設計について徹底解説!

  • FastAPI のルーティングの方法が知りたい
  • バリデーションってどうやるの?

このような疑問にお答えします。

FastAPI では関数を使ってルーティングを表現するので、端的で読みやすいです。

本記事ではルーティングにまつわる以下のような内容を解説します。

  • 各パラメータの見分け方
  • エンドポイントの作成方法
  • パスパラメータを扱う方法
  • リクエストボディを扱う方法
  • エンドポイントの柔軟性を高める設計のヒント

FastAPIを初めて触る方でも理解しやすいよう、できるだけ平易に解説していきます。

FastAPI での各パラメータの見分け方

パラメータには以下の種類があります。

  • パスパラメータ
  • クエリパラメータ
  • リクエストボディ

FastAPI のコードは一見するとこれらの違いが分かりにくいので、ここで見分け方を解説します。

パスパラメータ

パスパラメータは、URLのパスの一部として表現されます。

つまりURL 定義をしている部分のうち、波括弧({})の中身がパスパラメータです。

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

上記の例では、user_idがパスパラメータになります。

このパスパラメータを関数内で扱うため、「デコレータの波括弧内で宣言した変数名」と同じものを関数の引数として指定しています。

クエリパラメータ

クエリパラメータは URL の?の後に指定されます。
Key と Value の組み合わせで表現されるタイプのパラメータです。

FastAPI では、関数の引数がデフォルト値を持つ場合にクエリパラメータと解釈します。

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

skiplimitはデフォルト値が設定されているので、どちらもクエリパラメータです。

これはフィルタや設定オプションに使われることが多いです。

リクエストボディ

リクエストボディは「リクエストの本体部分に含まれるデータ」で、JSON などの形式で送信されることが多いです。

FastAPI では、Pydantic の BaseModel を継承したクラスを引数とした場合にリクエストボディと判定されます。

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str

@app.post("/items/")
async def create_item(item: Item):
    return item

上記の例を見てみると、itemという引数は BaseModel を継承した Item クラスを型に指定しています。

そのため、itemはリクエストボディから取得されます。

リクエストボディは POST, PUT メソッドで使われます。

FastAPI におけるエンドポイントの作成方法

基本的なルート設定方法

FastAPI では「各HTTPメソッドごとに用意されているデコレータ」を使います。

非同期通信を行うため、async defを使っていきます。

パスパラメータやリクエストボディは関数の引数で指定します。

GET リクエスト

GET メソッドはリソースを読み出すためのメソッドです。

from fastapi import FastAPI


app = FastAPI()

@app.get("/items")
async def read_items():
    return {"message": "GETリクエストを受け取りました"}

リソースに変更を加える場合には、POST メソッド以降を利用します。

POST リクエスト

新しいリソースを作成する場合に使われるのが POST メソッドです。

以下のように記述することで、POST リクエストに含まれるリクエストボディitemを受け取ることができます。

from fastapi import FastAPI


app = FastAPI()

@app.post("/items")
async def create_item(item: dict):
    return {"message": "アイテムを作成しました", "item": item}

受け取ったJSON に対して、便宜的にitemという名前をつけたと考えると理解しやすいです。

つまり、クライアント側からは POST メソッドで以下のような JSON をリクエストボディに含めることになります。

{
  "name": "example item",
  "price": 1500
}

FastAPI 側ではこの JSON を Python の辞書オブジェクトとして解釈することになります。

もちろん「パスパラメータ」や「クエリパラメータ」を含めることもできます。

PUT リクエスト

PUT はリソースを作成または置換するために使用されます。
リソースがすでにある場合は更新し、ない場合は新規作成します。

from fastapi import FastAPI


app = FastAPI()

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: dict):
    return {"message": "アイテムを更新しました", "item_id": item_id, "item": item}

上記では、「リクエストボディ」と「パスパラメータ」からデータを受け取るようになっています。

DELETE リクエスト

DELETE はリソースを削除するメソッドです。

from fastapi import FastAPI


app = FastAPI()

@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"message": "アイテムを削除しました", "item_id": item_id}

特定のitem_idに対してリソースの削除をするコード例になります。

実際にリソースの削除をするためには、関数内でレコード削除のビジネスロジック追加が必要です。

FastAPI でパスパラメータを扱う方法

パスパラメータは「URL の一部に動的な値」を指定する方法になります。

パスパラメータは渡されないと「不完全なURL」とみなされる点には注意が必要です。

パスパラメータの基本的な使い方

FastAPI では、「デコレータ内」と「関数の引数」でパスパラメータを定義します。

以下はパスパラメータとしてitem_idを定義した例です。

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id, "description": "こちらがアイテムの説明です。"}

関数の引数に型注釈としてintが指定されているので、FastAPI は以下のことを行います。

  • item_idは整数型に変換される
  • 整数型にできなければエラーを返す

例えば型注釈としてintを指定したにも関わらず “abc” を渡した場合には、”422 Unprocessable Entity” といステータスコードを返却します。

型注釈に指定できるデータ型

使えるデータは幅広く、例えば次のようなものがあります。

  • Python 標準のデータ型(str, float, list, set など)
  • Pydantic のデータ型(BaseModel, EmailStr など)
  • オプショナル型、リテラル型、ユニオン型

2つ以上のパスパラメータを扱う方法

複数のパスパラメータを扱う場合も、考え方は同じです。

パスパラメータとしてuser_iditem_idの二つを持つ場合の例を示します。

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: int):
    return {"user_id": user_id, "item_id": item_id, "description": "ユーザーとアイテムの情報"}

パスパラメータにスラッシュを含む場合の対策

/folder1/file1.txtというまとまりをパスパラメータとして取得したい場合を考えてみましょう。

普通にコードを書いてしまうと「スラッシュ」が境界線と判断されるので、folder1file1.txtが別の要素として取得されてしまいます。

これを一つのパスパラメータとして扱うには、”path” タイプを指定します。

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

こうすることでfile_pathに対して/folder1/file1.txtが渡されても、スラッシュで分割されずに要素を取得できることになります。

FastAPI でクエリパラメータを扱う方法

FastAPIでは、関数の引数にデフォルト値を設定することで、クエリパラメータになります。

つまり、クエリパラメータが省略された場合にはデフォルト値が使用されます。

基本的なクエリパラメータの受け取り方

skiplimitには初期値を設定したので、それぞれ二つの要素ともクエリパラメータとして扱われます。

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

クライアント側から見ると、以下のようなリクエストが送信されることを想定。

/items/?skip=20&limit=10

上記の場合、FastAPI は “skip” には “20” を、”limit” には “10” を受け取ることになります。

クエリパラメータをオプショナルにする

オプショナルにするには、初期値としてNoneを指定します。

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10, tag: Optional[str] = None):
    items = {"skip": skip, "limit": limit}
    if tag:
        items.update({"tag": tag})
    return items

Noneを書かない場合、必須のクエリパラメータとなります。

クエリパラメータのバリデーション

型宣言を使用してバリデーションが可能です。

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

この例では、以下のバリデーションを追加しました。

  • skip: 整数、デフォルト値は0
  • limit: 整数、デフォルト値は10

デフォルト値とは、クエリパラメータが渡されない場合の初期値です。

Pydantic を使ったより複雑なバリデーション

Pydanticの Field を使うことで、より複雑なバリデーションを行えます。

以下の例は、クエリパラメータで受け取る数値に「最小値」と「最大値」を設定しています

from pydantic import BaseModel, Field
from typing import Optional

class QueryParams(BaseModel):
    skip: int = Field(default=0, ge=0)
    limit: int = Field(default=10, ge=1, le=100)

@app.get("/items/")
async def read_items(query: QueryParams = Depends()):
    return {"skip": query.skip, "limit": query.limit}

このコードで行なっていることは以下です。

  • skip: 0以上
  • limit: 1以上100以下

ちなみに、数値の比較に使われる名前は以下の意味があるそうです。

  • ge(>=): greater than or equal
  • gt(>): greater than
  • le(<=): less than or equal
  • lt(<): less than

どれがどの効果を持つか思い出す際に便利です。

FastAPI でリクエストボディを扱う方法

FastAPI では、リクエストボディに対して複雑なバリデーションを行うことができます。

Pydanticモデルを定義して「型宣言」と「バリデーションルール」を組み込むことで対応することになります。

以下の例ではItemというリクエストボディのバリデーションを行っています。

from fastapi import FastAPI
from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str
    description: str = None
    price: float = Field(gt=0, description="The price must be greater than zero")
    tax: float = None

app = FastAPI()

@app.post("/items/")
async def create_item(item: Item):
    return {"name": item.name, "price": item.price}

Itemクラス内の要素の定義は以下です。

  • name: 必須項目
  • description: オプショナル
  • price: 0より大きい値
  • tax: オプショナル

初期値ありは必須項目、初期値なしはオプショナル(任意)の項目ということになります。

上記のようにField関数を使用すると、詳細なバリデーションルールやメタデータをフィールドに追加できます。

エンドポイントの柔軟性を高める設計のヒント

API をより使いやすくするためのヒントをご紹介します。

リソース指向のアプローチ

各HTTPメソッドの意味に沿った処理内容にしましょう。

HTTPメソッドと処理内容の関係は以下の通りです。

HTTPメソッド処理内容
GETリソースを読み出すが変更はしない
POST新規リソースを作成
PUTリソースの更新 or 置換
DELETEリソースの削除
PATCHリソースの部分的な更新
HTTPメソッドの処理内容

これらを遵守することで「直感的で分かりやすいエンドポイント」が作成できます。

「単一責任の原則」の観点からも重要な事項です。

パスパラメータとクエリパラメータを使い分ける

「パスパラメータ」と「クエリパラメータ」はそれぞれ用途や特性が異なります。

基本的には以下の使い分けがされることが多いです。

  • パスパラメータ: リソースの「特定」
  • クエリパラメータ:リソースの「操作」

リソースの特定とは、例えば「ユーザーID」や「製品ID」などを指定して、個別具体的なアイテムを取得してくるイメージです。

一方のリソースの「操作」とは、「ページネーション」「フィルタリング」「ソート」などのリソースに対する加工のための追加情報を渡す形になります。

バリデーションとエラー処理の強化

FastAPI では、Pydantic モデルを使った強力なバリデーションが可能です。

不正 / 不完全なリクエストに適切なエラーを返すことで、システム全体としてのセキュリティを上げていきましょう。

バージョニング

将来的な API の変更に備え、最初からバージョニングを組み込みましょう。

URL にバージョン番号を含めることで API の新旧バージョンを並行して運用することや、徐々に移行することもできるようになります。

セキュリティと認証

OAuth2 や JWT を活用し、安全なAPIエンドポイントを作成しましょう。

API ルーティングの知識を設計に生かそう!

FastAPI を使った API ルーティングの基礎をお伝えしてきました。

以上の内容を理解して上手に API 設計を行なっていきましょう。

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Python(Django)が得意。最近ではフロント側の技術に触れる機会も増えてきました。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)