Developersをフォローする

【FastAPI】FastAPIによるお手軽!型定義共有

バックエンド

今回はFastAPIを使用することでお手軽に型定義をフロントエンドと共有できるというところを紹介します!

作ったもの

まず、今回実装したものについて紹介させてください。
実装したものは、時間の差分を計算して10進法表記にして表示するシンプルなアプリケーションです。

アプリリンク: https://calculation-tools.vercel.app/overtime-calculations
バックエンド: https://github.com/ryu-0729/calculation-tools-api
フロントエンド: https://github.com/ryu-0729/calculation-tools

使用した技術

バックエンド

FastAPI / Python
Cloud Run

フロントエンド

Next.js / TypeScript
Vercel

FastAPIによるOpenAPIドキュメントの作成方法

FastAPIでのOpenAPIドキュメントの作成は簡単で、主に型ヒントを使用してAPIを実装してあげれば出来上がります。

時間差分取得API

from datetime import date, datetime

from fastapi import APIRouter, Depends, HTTPException, status

from app.schemas import timedifference

router = APIRouter()


@router.get("/")
def get_time_difference(
    req: timedifference.GetTimeDifferenceRequest = Depends(),
) -> timedifference.GetTimeDifferenceResponse:
    # datetime.timeオブジェクトでは日付の差分が計算できないのでdatetime.datetimeを使用する
    now_date = date.today()
    date_value = [now_date.year, now_date.month, now_date.day]

    start_datetime = datetime(
        *date_value,
        hour=int(req.start_hour.value),
        minute=int(req.start_minute.value),
    )
    end_datetime = datetime(
        *date_value,
        hour=int(req.end_hour.value),
        minute=int(req.end_minute.value),
    )

    if end_datetime < start_datetime:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=[
                {
                    "loc": ["start_hour, start_minute, end_hour or end_minute"],
                    "msg": "Please confirm your entry.",
                    "type": "value_error",
                }
            ],
        )

    timedelta_value = end_datetime - start_datetime

    return {"overTime": f"{timedelta_value.seconds / 3600}h"}

FastAPIにはデフォルトでSwagger UIやReDocが用意されているためそこにアクセスします。
/docsや/redocになるかと思います。

APIも特に何も設定することなく実行することができます!

OpenAPIドキュメントを定義しているファイルですが、/openapi.jsonにアクセスするとjson形式で表示されます。
※今回はお手軽に型定義を取得したかったので取得用のスクリプト等は記述していません。

後はコピー&ペーストでjsonファイルを作成します。
作成したjsonファイルを使用してフロントエンドとの型定義の共有を行います。

技術的な補足

1点やっといた方が良い設定があります。
デフォルトの状態だと、リクエストやレスポンスの値がスネークケースのままになります。
TypeScriptだとキャメルケースで変数名を定義することが多いと思います。
APIのリクエストやレスポンスの値を参照するときだけスネークケースで記述するみたいなことはかっこ悪いので、OpenAPIドキュメントの方もキャメルケースで定義するようにします。

設定方法も簡単で、Pydanticで定義するBaseModel内のmodel_configにalias_generatorを設定してあげます。
参考リンク: https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.alias_generator

ただ、毎回記述するのは面倒なので今回はカスタムのベースモデルを作成しています。

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel


class CustomBaseModel(BaseModel):
    model_config = ConfigDict(alias_generator=to_camel)

その他にもお手軽にデプロイしたいということでCloud Runを使用しています。
詳しい説明は割愛しますが、デプロイ自体はコマンドを一発叩くだけです。
※ 本当はGitHubと連携して自動デプロイを設定したかったのですが、設定する際にエラーで時間がかかってしまったので今回は諦めました。。

gcloud run deploy --source

参考リンク: https://cloud.google.com/run/docs/deploying-source-code?hl=ja

フロント側での型定義取得/使用方法

型定義取得

フロント側での型取得も比較的シンプルだと思います。
まず、ライブラリをインストールします。
使用したライブラリ: https://github.com/openapitools/openapi-generator-cli

yarn add @openapitools/openapi-generator-cli

READMEにも記載がありますが、Javaが必要なのでインストールしてください。
バージョン11で動作確認ができています。

後は、インポート先や出力先を指定したコマンドを記述していきます。

{
  "name": "calculation-tools",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "openapi:generate": "openapi-generator-cli generate -g typescript-axios -i ./config/openapi.json -o ./src/types/axios"
  },
  "dependencies": {
    "@openapitools/openapi-generator-cli": "^2.7.0",
    "axios": "^1.6.2",
    "next": "12.1.0",
    "pokenode-ts": "^1.13.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "swr": "^2.2.4"
  },
  "devDependencies": {
    "@types/node": "17.0.21",
    "@types/react": "17.0.41",
    "eslint": "8.11.0",
    "eslint-config-next": "12.1.0",
    "typescript": "4.6.2"
  }
}

scriptsにopenapi:generateを追加しています。

ここまで出来たら後は、「FastAPIによるOpenAPIドキュメントの作成方法」で作成したjsonファイルをインポート先においてコマンドを実行するだけです。

yarn openapi:generate

実行後は指定した場所に型定義ファイルが作成されます。
詳しくは今回作成したフロントエンドのリポジトリの/src/types/axiosを見ていただければと思います。
https://github.com/ryu-0729/calculation-tools/tree/master/src/types/axios

型定義の使用方法

APIを使用するカスタムHook

import useSWR from 'swr';
import { KeyedMutator } from 'swr';

import {
  TimeDifferenceApi,
  GetTimeDifferenceResponse,
  TimeDifferenceHour,
  TimeDifferenceMinute,
} from '../../types/axios';

import { config } from '../config';

type UseGetTimeDifferenceResponse = {
  data?: GetTimeDifferenceResponse
  error: any
  mutate: KeyedMutator<any>
};

const timeDifferenceApi = new TimeDifferenceApi(config);

export type GetTimeDifferenceParam = {
  startHour: TimeDifferenceHour
  startMinute: TimeDifferenceMinute
  endHour: TimeDifferenceHour
  endMinute: TimeDifferenceMinute
};

export const useGetTimeDifference = ({
  startHour, startMinute, endHour, endMinute
}: GetTimeDifferenceParam): UseGetTimeDifferenceResponse => {
  const fetcher = () => (
    timeDifferenceApi.getTimeDifferenceTimedifferenceGet(startHour, startMinute, endHour, endMinute)
      .then((res) => res.data)
      .catch((err) => err)
  );

  const { data, error, mutate } = useSWR("/timedifference/", fetcher);

  return {
    data,
    error,
    mutate,
  };
};

データの取得にはSWRを使用しているので、ソースの記述はSWRでの使用例になります。
SWR: https://swr.vercel.app/ja

主に、先ほど取得したリクエストやレスポンスの型定義を使用してカスタムHookを定義しています。
このカスタムHookを使用したいコンポーネントで呼び出すことでAPIを安全に利用することができるようになっています!

まとめ

FastAPIを使用することでお手軽に型定義をフロントエンドと共有することができるということを紹介させていただきました!
OpenAPIドキュメントをデフォルトで出力してくれるのはかなり便利ですし、安全にAPIを使用することもできるので開発効率もかなり変わってくるのではないかと考えております。

以上、FastAPIによるお手軽!型定義共有でした!