エンドポイントの作成
ここでは、ユーザーデータを作成、取得、更新、削除を行うエンドポイントを作成してきます。初めに FastAPI のパラメータ取得方法を解説しますが、公式サイトで既に知っている方は、スキップしてください。
目次
- パラメータの取得(スキップ可)
- 作成するエンドポイント
- スキーマの定義
- パスオペレーション関数の定義
- パスオペレーション関数のdescription
- Next: Chapter4 DBとの連携
- Prev: Chapter2 ディレクトリ構成
パラメータの取得(スキップ可)
FastAPI におけるパスパラメータ、クエリパラメータ、リクエストボディの取得の仕方について説明します。公式サイトのチュートリアルでも分かりやすく説明されているので、公式サイトも参照してください。ここでは、簡単に説明します。
パスパラメータ
パスパラメータは、format 文字列と同様のシンタックスで取得できます。app/api/endpoints/root.pyを以下のように編集してみましょう。
app/endpoints/root.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/")
def root():
    return {"API": "Active"}
@router.get("/items/{item_id}")
def read_item(item_id):
    return {"item_id": item_id}
サーバーを起動(uvicorn app.main:app --reload --port 8000実行)後、http://127.0.0.1:8000/items/fooにアクセスすると以下のようなレスポンスが表示されるはずです。
{ "item_id": "foo" }
また、以下のようにすることで、パスパラメータの型を宣言することができ、その型に自動的に変換されます。
@router.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}
サーバーを起動後、http://127.0.0.1:8000/items/3にアクセスすると以下のようなレスポンスが得られます。
{ "item_id": 3 }
型を宣言すると、バリデーションも行うので、http://127.0.0.1:8000/items/fooにアクセスすると、以下のような HTTP エラーが表示されます。
{
  "detail": [
    {
      "type": "int_parsing",
      "loc": [
        "path",
        "item_id"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "foo"
    }
  ]
}
クエリパラメータ
パスパラメータではないものをパスオペレーション関数の引数とすると、自動的にクエリパラメータとして解釈されます。
app/endpoints/root.pyの末尾に以下を追加してみましょう。
@router.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
    fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
    return fake_items_db[skip : skip + limit]
次の URL にだと
http://127.0.0.1:8000/items/?skip=1&limit=20
クエリパラメータは、skipは1、limitは20と解釈されます。また、今回は型も宣言されているので、バリデーションも行われます。ちなみにリクエストに対するレスポンスは、以下のようになります。
[{ "item_name": "Bar" }, { "item_name": "Baz" }]
リクエストボディ
クライアント (ブラウザなど) から API にデータを送信する必要があるとき、データを リクエストボディ (request body) として送ります。
FastAPI では、リクエストボディは pydantic を利用して型を定義します。この型を定義は、スキーマと呼びapp/schemaディレクトリにファイルを作っていきます。以下のファイルを作成しましょう。
app/schemas/root.py
from pydantic import BaseModel
class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
また、app/api/endpoints/root.pyで定義したスキーマをインポートし、パスオペレーション関数を追加しましょう。
app/api/endpoints/root.py
from app.schemas.root import Item
@router.post("/items/")
def create_item(item: Item):
    return item
さて、このエンドポイントを試すためには、リクエストボディを記述する必要があります。その場合は Swagger UI を使いましょう。サーバーを起動後、http://127.0.0.1:8000/docsにアクセスしましょう。先ほど作成したエンドポイントPOST /items/を開き、Try it outボタンを押しましょう。以下のような画面になるので、リクエストボディを自由に編集することができます。自分の好きなパラメータで試してみましょう。

作成するエンドポイント
ここからは、ユーザーデータの追加、取得、更新、削除を行うエンドポイントを作成します。 ユーザーデータは、本来 DB に保存しますが、それは後で行います。
- POST- /usersユーザーデータの作成
- GET- /usersユーザーデータの取得
- PUT- /users/{singin_id}ユーザーデータの更新
- DELETE- /users/{sinin_id}ユーザーデータの削除
それでは、まずパスオペレーション関数を定義していきましょう。今回作成するエンドポイントは、ユーザー関連のエンドポイントなので、共通のパス/usersをつけています。この場合、ルーターをまとめる際にprefixで定義します。パスオペレーション関数では、/usersを除いた部分のみ定義します。以下のファイルを作成してください。
app/api/endpoints/users.py
from fastapi import APIRouter
router = APIRouter()
@router.post("")
def create_user():
    pass
@router.get("")
def read_user():
    pass
@router.put("/{signin_id}")
def update_user():
    pass
@router.delete("/{signin_id}")
def delete_user():
    pass
このルーターをprefix = "/users"で追加します。app/api/api.pyを以下のように編集してください。
app/api/api.py
from fastapi import APIRouter
from app.api.endpoints import root, users
api_router = APIRouter()
api_router.include_router(root.router, tags=["test"])
api_router.include_router(users.router, tags=["users"], prefix="/users")
これで、一度サーバーを起動して、Swagger UI を見てみましょう。

新しく定義されたエンドポイントが追加されているはずです。
スキーマの定義
パスオペレーション関数の具体的な定義を書く前に、リクエストボディやレスポンスボディのスキーマを定義していきます。
リクエストボディでも説明しましたが、FastAPI では、Pydantic を利用して API のリクエストとレスポンスの型のバリデーションを行います。この型定義のことをスキーマと呼んでいます。
さて、リクエストボディが必要になるエンドポイントは、以下2つです。
- POST- /usersユーザーデータの作成
- PUT- /users/{singin_id}ユーザーデータの更新
スキーマをそれぞれUserCreate、UserUpdateとして定義します。スキーマの定義は、app/schemasディレクトリで行います。以下のファイルを追加してください。
app/schemas/user.py
from typing import Optional
from pydantic import BaseModel
class UserCreate(BaseModel):
    signin_id: str
    password: str
    name: Optional[str] = ""
    role: Optional[str] = "User"
class UserUpdate(BaseModel):
    signin_id: str | None = None
    password: str | None = None
    name: str | None = None
また、レスポンスボディとして返すスキーマも定義します。password をレスポンスとして返すわけにはいかないので、password 以外の閲覧してもいいようなデータをレスポンスとして返しましょう。app/schemas/user.pyに以下のスキーマを追加してください。
app/schemas/user.py
class UserResponse(BaseModel):
    signin_id: str
    name: str
    role: str
ここで、定義したスキーマをパスオペレーション関数で使っていくのですが、パスオペレーション関数などで、import した際に、このクラスがスキーマであることを認識したいです。今後、DB などの連携を行うと DB のクラスなども import するため、スキーマなのか DB のクラスなのか混乱させないためです。
そのため、他のファイルでは、schemas.UserCreateという参照の仕方をしたいです。これを行うには、app/schemas/__init__.pyを以下のように編集してください。
app/schemas/__init__.py
from .user import UserCreate, UserUpdate, UserResponse
パスオペレーション関数の定義
ここから、パスオペレーション関数の定義をしていきましょう。ユーザーデータは本来、DB に格納しますが、ここでは、疑似的な DB としてリストに保持することとします。以下のようにapp/api/endpoints/users.pyに疑似的な DB を書きます。
app/api/endpoints/users.py
#  疑似的なDB
from pydantic import BaseModel
class User(BaseModel):
    signin_id: str
    password: str
    name: str
    role: str
fake_user_db = [User(signin_id="tarou", password="tarou", name="太郎", role="User")]
そして、app/api/endpoints/users.pyにて以下を追加し、スキーマを import しておきましょう。
app/api/endpoints/users.py
from app import schemas
それでは、エンドポイントを順に書き換えて行きましょう。
- 
    POST/usersユーザーデータの作成@router.post("", response_model=schemas.UserResponse) def create_user(user_create: schemas.UserCreate): user = User(**user_create.model_dump()) fake_user_db.append(user) return user
- 
    GET/usersユーザーデータの取得from typing import List @router.get("", response_model=List[schemas.UserResponse]) def read_user(skip: int = 0, limit: int = 100): return fake_user_db[skip : skip + limit]
- 
    PUT/users/{singin_id}ユーザーデータの更新from fastapi import HTTPException @router.put("/{signin_id}", response_model=schemas.UserResponse) def update_user(signin_id: str, update_user: schemas.UserUpdate): update_dict = {} for column, value in update_user: if value is not None: update_dict[column] = value user_ids = [user.signin_id for user in fake_user_db] if signin_id not in user_ids: raise HTTPException(status_code=404, detail="User not found") index = user_ids.index(signin_id) new_user = User(**fake_user_db[index].model_dump()) for key in update_dict: setattr(new_user, key, update_dict[key]) fake_user_db[index] = new_user return new_user
- 
    DELETE/users/{sinin_id}ユーザーデータの削除@router.delete("/{signin_id}", response_model=None) def delete_user(signin_id: str): user_ids = [user.signin_id for user in fake_user_db] if signin_id not in user_ids: raise HTTPException(status_code=404, detail="User not found") index = user_ids.index(signin_id) del fake_user_db[index] return None
これで完了です。レスポンスのスキーマは、@router.get(...)などにキーワード引数response_modelとして渡しています。ユーザーデータの取得を例として挙げると、以下の通りです。
@router.get("", response_model=List[schemas.UserResponse])
def read_user(skip: int = 0, limit: int = 100):
    return fake_user_db[skip : skip + limit]
response_modelを指定してあげると、自動的にスキーマに変換してくれます。上の関数で、fake_user_dbは、List[User]という型ですが、response_modelを定義したことにより、List[schemas.UserResponse]に変換されてレスポンスが返されます。
SwaggerUIで、いろいろ試して動作を確認してみましょう。
パスオペレーション関数のdescription
パスオペレーション関数にpythonのdocstringsをつけることで、SwaggerUIにdescriptionを追加することができます。 もちろん、pythonのdocstringなので、pythonのドキュメントにもなります。積極的にdocstringsを付けていきましょう。
ここでは、例としてcreate_userにdocstringsを付けてみます。
app/endpoints/users.py
@router.post("", response_model=schemas.UserResponse)
def create_user(user_create: schemas.UserCreate):
    """
    ユーザーの作成
    """
    user = User(**user_create.model_dump())
    fake_user_db.append(user)
    return user
SwaggerUIでは、以下のようになります。

また、SwaggerUIはmarkdown記法にも対応しています。
app/endpoints/users.py
@router.post("", response_model=schemas.UserResponse)
def create_user(user_create: schemas.UserCreate):
    """
    ユーザーの作成
    - **singin_id**: ユニークなID
    - **name**: 名前
    - **password**: パスワード
    - **role**: 権限
    """
    user = User(**user_create.model_dump())
    fake_user_db.append(user)
    return user
このようにmarkdown記法で記述するとSwaggerUIでは、以下のように表示されます。
