FastAPIは、PythonでWeb APIを作るためのフレームワークです。
FastAPIの大きな特徴は、Pythonの型ヒントをそのまま活用する点です。型ヒントは、変数や関数の引数に「どんな種類のデータか」を書き添える記法のことで、例えば、name: strは「変数nameを文字列とする」という意味になります。FastAPI はこの型ヒントを読み取って、送られてきたデータが正しい形かを自動でチェックし、さらに API の説明ドキュメントまで自動生成します。
もう1つの特徴は、非同期処理を書きやすい点です。API処理では外部に問い合わせを行い、結果を待つ時間が生じます。その待ち時間に別のリクエストを処理することを非同期処理といいます。I/O の待ちが多い API では、非同期処理を行うことで全体の効率が上がります。
本記事では、FastAPIで非同期のエンドポイント(リクエストを受け取る関数)を定義し、「書籍」を扱うAPIを例に、Pydanticでの型付け、パスパラメータ、エラー応答およびドキュメントの自動生成について、順を追って解説します。
※ この記事の動作確認は2026年6月中頃に実施しています。
目次
- 事前準備
- 最小アプリと起動
- Pydanticでリクエストのデータを定義する
- GET・POSTエンドポイントとパスパラメータ
- HTTPException でエラーを返す
- 動作させてみる
- 自動生成ドキュメント(/docs)で動かす
- まとめ
事前準備
FastAPI と、開発に使うサーバーをまとめてインストールします。
# pip を使っている場合
pip install "fastapi[standard]"
# uv を使っている場合
uv add "fastapi[standard]"fastapi[standard]の[standard]を付けると、FastAPI本体に加えて、Uvicorn(ユビコーン)というサーバーや、後で使うfastapiコマンド(FastAPI CLI)などがまとめて入ります。
Uvicorn はASGI サーバーの一つです。ASGIとは、Pythonの非同期処理(async/await)に対応したWebアプリとサーバーの接続仕様のことです。ASGIはAsynchronous Server Gateway Interfaceの略です。
本記事のコードは Python 3.10 以上を前提とします。
最小アプリと起動
一番小さなFastAPIアプリから作成してみます。main.py というファイルを用意して、次のコードを書きます。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, FastAPI"}ポイントを以下に記述します。
■ app = FastAPI()
アプリ本体を作って、変数appに入れています。以降はこのappにエンドポイントを追加していきます。
■ @app.get("/")
この@で始まる行をデコレータといい、「直下に記述した関数に役割を与える印」です。ここでは「パス/へのGETリクエストが来たら、すぐ下のroot関数で処理する」という意味になります。
関数が返した辞書{"message": "Hello, FastAPI"}は、FastAPIが自動的にJSON形式に変換して、レスポンスとして返します。
開発用のサーバーは、次のコマンドで起動します。
fastapi dev main.py
# uv の 場合
uv run fastapi dev main.py起動すると http://127.0.0.1:8000 で待ち受けます。127.0.0.1 は「自分自身のマシン(localhost)」を指すアドレス、8000 は ポート番号(同じマシン上で複数のサービスを区別するための番号)です。ブラウザでこの URL を開くと、{"message":"Hello, FastAPI"} と表示されます。fastapi dev は開発用のモードで、コードを保存するたびにサーバーを自動で再起動(オートリロード)してくれます。
fastapi dev main.py は、内部では Uvicorn を呼び出しているだけです。次のコマンドとほぼ同じ意味なので、どちらを使っても構いません。
uvicorn main:app --reload
# uv の場合
uv run uvicorn main:app --reloadmain:app は「main.py というファイルの中の app という変数」を指す書き方で、--reload がオートリロードの指定です。
起動したサーバーはCtrl + Cで止めることができます。
Pydanticでリクエストのデータを定義する
POSTリクエストなどで送られてくる本体のデータ(リクエストボディ)は、Pydanticというライブラリで受け取ります。Pydanticを使うと、「データがどんな項目を持ち、それぞれどんな型か」をクラスとして定義できます。そして、送られてきたデータがその通りかを自動でチェックしてくれます。このチェックはプログラムの実行中に行われます。つまり、実際にリクエストが届いたときに、そのデータが定義した型と合っているかを確認します。
使い方はBaseModel というクラスを継承して自分のクラスを作り、それをエンドポイント関数の引数に型として指定するだけです。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Book(BaseModel):
title: str
author: str
year: int | None = None
@app.post("/books")
async def create_book(book: Book):
return bookBookクラスにBaseModelクラスを継承させ、リクエストのフォーマットとして3つのフィールドとその型を定義しています。例えば、year: int | None = Noneはyearという変数が、整数型またはNoneを取り、省略された場合(デフォルト)はNoneであることを意味しています。
@app.postはパス(/books)にPOSTリクエストが来たときに実行される関数を表すデコレータです。その関数create_bookの引数を book: Bookと書くと、FastAPI はリクエストボディのJSONを読み取り、Bookの形に当てはめて検証してから関数に渡してくれます。型が合わない、あるいは必須フィールドが足りない場合は、FastAPIが自動的に422というエラーと、どこが問題かを示すJSONを返します。検証のコードを自分で書く必要はありません。
たとえば次のような JSONをPOSTすると、FastAPIによる型チェックを通過して、そのままJSON形式でレスポンスとして返されます。
{ "title": "吾輩は猫である", "author": "夏目漱石", "year": 1905 }また、"title"部分をあえて削除して送ると、HTTPステータスコード422と共に、エラーの詳細を示すJSONがレスポンスとして返されます。
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"title"
],
"msg": "Field required",
"input": {
"author": "夏目漱石",
"year": 1905
}
}
]
}GET・POSTエンドポイントとパスパラメータ
「書籍を登録する・一覧を取得する・1件だけ取得する」という小さなAPIを作成します。データはメモリ上の辞書に保存することにします。プログラムを再起動すると消えてしまいますが、本記事の趣旨から問題ないものとします。
レスポンスにも型をつけたいので、BookResponseクラスを用意します。先ほどのBookを継承して、サーバー側で割り振るid(識別番号)のフィールドを足したものです。
class BookResponse(Book):
id: int
books: dict[int, BookResponse] = {}books: dict[int, BookResponse]={}は、「キーが int(id)、値がBookResponse」である空の辞書を定義しています。これに書籍一覧を格納します。
書籍一覧の取得と書籍の登録を行うAPIの作成
次の2つのAPIを追加します。
@app.get("/books")
async def list_books() -> list[BookResponse]:
return list(books.values())
@app.post("/books")
async def create_book(book: Book) -> BookResponse:
new_id = max(books.keys(), default=0) + 1
new_book = BookResponse(id=new_id, **book.model_dump())
books[new_id] = new_book
return new_book書籍一覧の取得を行うAPI(list_books)と書籍の登録を行うAPI(create_book)です。ポイントは以下の2つです。
1つ目は、戻り値の型注釈です。関数の後ろの-> list[BookResponse]は「BookResponseが入ったリスト」を戻り値として返すという意味ですが、FastAPI はこの戻り値の型を、単なるメモ書きではなく「レスポンスの型」としても使います。
2つ目は、番号の割り振りです。max(books.keys(), default=0) は辞書booksのキーを取得して、その最大値を取得しています。つまり、現在登録されている書籍のidの最大値を求めています。そして、「今ある id の最大値に 1 を足したもの」を新しい id にしています。
BookResponse(id=new_id, **book.model_dump()) の ** は、辞書をキーワード引数として展開する書き方です。model_dump() はBaseModelクラスから継承されたメソッドで、定義されたモデル(ここではBookクラスのオブジェクト)を辞書に変換します。これにより、book.model_dump() が {"title": ..., "author": ..., "year": ...} という辞書を返すので、この行は実質的に BookResponse(id=new_id, title=..., author=..., year=...) と同じ意味になります。受け取った Book の中身に id を足して、保存用の BookResponse を作っています。
なお、クライアントは id を送りません(id はリクエスト側Bookではなく、レスポンス側BookResponseで定義されたフィールドであるためです)。サーバーが採番して返す、という形になります。
パスパラメータを用いて書籍情報を取得する
URLの一部を変数として受け取るしくみをパスパラメータと呼びます。パスの中に波かっこ {} で名前を書き、同じ名前の引数に型を付けます(下記のbook_id: intの部分)。
@app.get("/books/{book_id}")
async def get_book(book_id: int) -> BookResponse:
if book_id not in books:
raise HTTPException(status_code=404, detail="指定された書籍が見つかりません")
return books[book_id]book_id: int と型を付けておくと、FastAPI は URL の中の文字列(たとえば /books/3 の "3")を整数 3 に変換してから渡してくれます。整数に変換できない値(たとえば /books/abc)が来た場合は、自動的に先ほどの 422 エラーになります。
存在しない id が指定されたときは、raise HTTPException(...) という1行で 404 を返しています。raise は「エラーを発生させる」命令です(詳しくは次の節で説明します)。HTTPException を使うには from fastapi import HTTPException の読み込みが必要です。
HTTPException でエラーを返す
API は、正常な結果だけでなく、「見つからなかった」「リクエストがおかしい」といった状況も、ふさわしい HTTP ステータスコードとともに返す必要があります。FastAPI では、そのために HTTPException を使います。前節の get_book では、存在しない id のときに次の1行でエラーを返していました。
raise HTTPException(status_code=404, detail="指定された書籍が見つかりません")ここで使った HTTPException は、from fastapi import HTTPException で読み込みます。渡している引数は2つです。
| 引数 | 説明 |
|---|---|
| status_code | 返す HTTP ステータスコードです。「見つからない」は 404、「リクエストの内容が不正」は 400 のように、状況に合ったものを選びます。 |
| detail | エラーの説明です。文字列のほか、JSON にできる値(辞書やリスト)も渡せます。クライアントには {"detail": "..."} という形の JSON で返ります。 |
この2つ以外に headers(レスポンスヘッダーの追加)も指定できますが、認証まわりなど発展的な場面で使うものなので、本記事では扱いません。
ここで大切なのは、HTTPExceptionはreturn(返す)のではなく raise(発生させる)という点です。HTTPExceptionはPythonの例外の一種で、raiseするとその場で処理が中断され、FastAPI がそれを受け取ってHTTP のエラー応答に変換してくれます。
raise で発生させることには利点もあります。エンドポイント関数から呼び出した別の補助関数の中で raise HTTPException(...) しても、その時点でリクエストの処理を打ち切ってエラーを返せます。「エラーだったかどうか」を表す値をいちいち戻り値で受け渡して、呼び出し元まで運ぶ必要がありません。
動作させてみる
ここまでをまとめた main.py の全体は、次のとおりです。これだけで動作します。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Book(BaseModel):
title: str
author: str
year: int | None = None
class BookResponse(Book):
id: int
books: dict[int, BookResponse] = {}
@app.get("/books")
async def list_books() -> list[BookResponse]:
return list(books.values())
@app.get("/books/{book_id}")
async def get_book(book_id: int) -> BookResponse:
if book_id not in books:
raise HTTPException(status_code=404, detail="指定された書籍が見つかりません")
return books[book_id]
@app.post("/books")
async def create_book(book: Book) -> BookResponse:
new_id = max(books.keys(), default=0) + 1
new_book = BookResponse(id=new_id, **book.model_dump())
books[new_id] = new_book
return new_book次のコマンドで起動します。
fastapi dev main.py
# uv の 場合
uv run fastapi dev main.py自動生成ドキュメント(/docs)で動かす
FastAPI は、これまで書いてきた型ヒントや Pydantic モデルをもとに、API のドキュメントを自動で生成します。
| 項目 | アクセス | 内容 |
|---|---|---|
| Swagger UI | http://127.0.0.1:8000/docs | 各エンドポイントがどんな入力を受け取り、どんな出力を返すかの一覧を確認でき、ブラウザ上から実際にリクエストを送って試せます。 |
| ReDoc | http://127.0.0.1:8000/redoc | こちらは読みやすく整理された、閲覧向けの仕様表示です。 |
たとえば /docs で POST /books を開き、title と author を入力して実行すると、id の付いた BookResponse が返ってきます。続けて GET /books/{book_id} にその id を入れれば、登録した書籍を取得できます。存在しない id を入れたときに、404 と detail のメッセージが返ることも確認できます。
このドキュメントはコードから自動生成されるため、エンドポイントやモデルを変更すれば、ドキュメントの内容も自動で追従します。仕様書を手作業で更新し続ける必要はありません。
まとめ
本記事では、FastAPIを用いた非同期エンドポイントの作成方法について解説しました。以下にポイントをまとめます。
- FastAPI のエンドポイントは、
@app.get(...)や@app.post(...)といったデコレータと関数で定義します。async defで書くと非同期の処理をawaitで待てます。一方、待つ処理がなければdefで書いても構いません(その場合は FastAPI がスレッドで実行します)。 - リクエストボディは、Pydantic の
BaseModelを継承したクラスで受け取ります。初期値のあるフィールドは省略可能になり、型が合わなければ FastAPI が自動で422を返します。 - 戻り値の型注釈(
-> BookResponseなど)は、メモ書きにとどまらず、レスポンスの型・検証・ドキュメントに使われます。 - パスパラメータは、パスに
{}で書き、同じ名前の引数に型を付けると、自動で変換・検証されます。固定パスは、変数のパスより先に宣言します。 - エラーは、
HTTPExceptionをraiseして返します(returnではありません)。status_codeとdetailを指定します。 /docs(Swagger UI)と/redoc(ReDoc)が自動生成され、ブラウザから API を試せます。