閉じる

FastAPI入門: Pythonで高速なWeb APIを最速で作る手順

Pythonで高速なWeb APIを最速で立ち上げたいなら、FastAPIが最有力候補です。

本稿では、環境構築から最小のAPI起動、GET/POSTの実装、型ヒントと自動バリデーション、ルータ構成、エラーハンドリングまでを段階的に解説します。

初心者でも迷わず動かせる具体的な手順を重視し、実行コマンドやレスポンス例も丁寧に示します。

FastAPIとは?Pythonで高速なWeb APIを最速で作る

FastAPIはStarlette(ASGI)ベースで動くPythonのWebフレームワークで、型ヒントを活用した宣言的な記述と自動ドキュメント生成が最大の特徴です。

非同期I/Oに対応し、モダンな書き方で高い開発体験を提供します。

低ボイラープレートで生産性が高いため、初めてのWeb API開発にも適しています。

FastAPIの強み 高速かつモダンな書き方

FastAPIの速度はASGIサーバ(Uvicorn)とStarletteの組み合わせによるものです。

非同期処理(Async)に標準対応し、I/O待ちが多いAPIにおいて高いスループットを得られます。

Pythonの型ヒントに準拠した宣言設計により、IDE補完や静的解析と相性が良い点も魅力です。

以下は主な特長の一覧です。

特長説明
高速ASGI(Uvicorn)+Starletteで非同期I/Oに強い
型ヒント駆動引数と戻り値の型からリクエスト/レスポンスを自動検証
自動ドキュメントOpenAPI仕様に基づいて/docs/redocを自動生成
バリデーションPydanticによる厳密なデータ検証と変換
モジュール性ルータ(APIRouter)でエンドポイントを整理しやすい

モダンな標準(ASGI・型ヒント・OpenAPI)に寄り添った設計が、継続的な拡張・保守にも効いてきます。

型ヒントで直感的に書ける 入門に最適

FastAPIでは、関数の引数や戻り値にPythonの型ヒントを付けるだけで、自動的にリクエストのパースと検証が行われます

たとえばitem_id: intと書けば、文字列が送られても整数に変換を試み、無理ならバリデーションエラーを返してくれます。

コードがドキュメントになり、バグも減らせます

Python
# 型ヒントのイメージ例
def calc_price_with_tax(price: float, tax: float | None = None) -> float:
    # 税率は省略可能(None)。指定がなければ0扱いにします。
    t = tax or 0.0
    return price * (1 + t)

自動ドキュメントで学びやすい OpenAPI対応

FastAPIはOpenAPI仕様のスキーマを自動生成します。

サーバを起動するだけで/docsにSwagger UI、/redocにReDocが利用可能です。

エンドポイントとスキーマを確認しながら試せるため、学習効率とチームの連携効率が高まります

Shell
# OpenAPI JSONを取得する例
curl -s http://127.0.0.1:8000/openapi.json | head -n 20

最速で作るHello Web API

ここでは、最小構成でAPIを立ち上げ、ブラウザとcurlで動作確認する手順を示します。

Python初心者を想定して、環境構築から順に進めます。

まずは動く体験を得ることが大切です。

FastAPIとUvicornのインストール

Python 3.10以上を推奨します。

仮想環境(venv)の利用をおすすめします。

必要最小限はfastapiとASGIサーバuvicornです。

Shell
# (任意) 仮想環境の作成と有効化
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate

# FastAPI本体とUvicornをインストール
pip install fastapi uvicorn[standard]

最小のmain.pyを作成する

最小のエントリポイントmain.pyを作ります。

まずはルート/だけにHelloを返すAPIです。

Python
# main.py
from fastapi import FastAPI

# アプリケーション本体を作成。タイトルやバージョンはOpenAPIにも反映されます。
app = FastAPI(title="Hello FastAPI", version="0.1.0")

# GET / にアクセスしたときにJSONを返す
@app.get("/")
async def read_root():
    # FastAPIでは辞書を返すとJSONに自動変換されます
    return {"message": "Hello, FastAPI!"}

uvicornで起動する コマンド例

直接python main.pyで実行してもAPIサーバーは立ち上がりません。

Uvicornでアプリを起動します。

モジュール名:アプリ変数の順で指定します。

今回の例だとmain.pymainがモジュール名main.py内にあるapp = FastAPI(...)appがアプリ変数です。

Shell
uvicorn main:app --port 8000
実行結果
# 代表的な起動ログ(環境により差異あり)
INFO:     Started server process [12345]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

ブラウザで確認する ルートと/docs

ブラウザでhttp://127.0.0.1:8000/にアクセスすると、HelloのJSONが表示されます。

ドキュメントはhttp://127.0.0.1:8000/docsですぐ確認できます。

リクエストをその場で送れるTry it outも便利です。

Shell
# ルートの動作確認
curl -s http://127.0.0.1:8000/
JSON
{"message":"Hello, FastAPI!"}

GETエンドポイントを追加する

パスパラメータとオプションのクエリ文字列を扱う例です。

型ヒントだけで自動バリデーションが効きます。

Python
# main.py (追記)
from typing import Optional

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
    # item_id は自動で整数にバリデートされます
    result = {"item_id": item_id}
    if q is not None:
        result["query"] = q
    return result
Shell
# 呼び出し例
curl -s "http://127.0.0.1:8000/items/42?q=fast"
JSON
{"item_id":42,"query":"fast"}

POSTでJSONを受け取る 基本の書き方

Pydanticモデルでリクエストボディの型と検証を定義します。

必須・最小値などの制約は<Field>で宣言的に指定できます。

Python
# main.py (追記)
from pydantic import BaseModel, Field

# リクエストボディのスキーマ定義
class Item(BaseModel):
    name: str = Field(..., min_length=1, description="商品名")
    price: float = Field(..., ge=0, description="税抜価格")
    tax: float | None = Field(None, ge=0, description="税率(0〜)")

@app.post("/items")
async def create_item(item: Item):
    # 税率が省略された場合は0とみなします
    tax = item.tax or 0.0
    price_with_tax = item.price * (1 + tax)
    # レスポンスは辞書でもPydanticモデルでもOK
    return {
        "name": item.name,
        "price_with_tax": round(price_with_tax, 2),
    }
Shell
# 正常なPOST
curl -s -X POST "http://127.0.0.1:8000/items" \
  -H "Content-Type: application/json" \
  -d '{"name":"Notebook","price":1000,"tax":0.1}'
JSON
{"name":"Notebook","price_with_tax":1100.0}
Shell
# priceに文字列など不正な値を送ると422エラー(自動バリデーション)
curl -s -X POST "http://127.0.0.1:8000/items" \
  -H "Content-Type: application/json" \
  -d '{"name":"X","price":"free"}'
JSON
{
  "detail": [
    {
      "type": "float_parsing",
      "loc": ["body", "item", "price"],
      "msg": "Input should be a valid number, unable to parse string as a number",
      "input": "free"
    }
  ]
}

実用に向けた基本テクニック

ここからは、現場で役立つ基本機能と設計のコツを解説します。

小さく始めて、整理しながら拡張するのが成功パターンです。

型ヒントと自動バリデーションの基本

FastAPIはPydanticを使い、型に基づく厳密な検証と自動変換を行います。

最小・最大、文字列長、正規表現などを宣言的に指定できます。

Python
# 型と制約の例
from pydantic import BaseModel, Field

class User(BaseModel):
    username: str = Field(..., min_length=3, max_length=20)
    age: int = Field(..., ge=0, le=130)
    email: str | None = Field(None, pattern=r".+@.+")  # 簡易なパターン例

@app.post("/users")
async def create_user(user: User):
    # user は検証済みデータ。型が保証され、安心して使えます
    return {"ok": True, "user": user}
Shell
# 不正データの例
curl -s -X POST "http://127.0.0.1:8000/users" \
  -H "Content-Type: application/json" \
  -d '{"username":"ab","age":-1,"email":"invalid"}'
JSON
{
  "detail": [
    {
      "type": "string_too_short",
      "loc": ["body","user","username"],
      "msg": "String should have at least 3 characters"
    },
    {
      "type": "greater_than_or_equal",
      "loc": ["body","user","age"],
      "msg": "Input should be greater than or equal to 0"
    },
    {
      "type": "string_pattern_mismatch",
      "loc": ["body","user","email"],
      "msg": "String should match pattern '.+@.+'"
    }
  ]
}

失敗は早く、理由は詳しく返るため、クライアント側の実装も進めやすくなります。

パスパラメータとクエリパラメータの扱い

パスは必須、クエリはオプションが基本です。

Path/Queryヘルパで範囲や説明を宣言できます。

Python
from fastapi import Path, Query

@app.get("/users/{user_id}")
async def get_user(
    user_id: int = Path(..., ge=1, description="ユーザID(1以上)"),
    keyword: str | None = Query(None, min_length=2, max_length=50, description="検索キーワード"),
    limit: int = Query(10, ge=1, le=100, description="最大件数(1〜100)")
):
    return {
        "user_id": user_id,
        "keyword": keyword,
        "limit": limit,
    }
Shell
curl -s "http://127.0.0.1:8000/users/7?keyword=hi&limit=5"
JSON
{"user_id":7,"keyword":"hi","limit":5}

入力仕様はコードに近い場所に書くことで、実装とドキュメントの乖離を防げます

レスポンスモデルで返却データを定義

レスポンスの型もモデルで固定できます。

返すフィールドを明示し、不要情報を遮断するのに有効です。

Python
from pydantic import BaseModel
from typing import List

class ItemIn(BaseModel):
    name: str
    price: float
    tax: float | None = None
    internal_note: str | None = None  # クライアントに返したくない内部情報

class ItemOut(BaseModel):
    id: int
    name: str
    price: float
    tax: float | None = None
    tags: List[str] = []

fake_db = []
id_seq = 0

@app.post("/catalog/items", response_model=ItemOut, response_model_exclude_none=True)
async def add_item(item: ItemIn):
    global id_seq
    id_seq += 1
    # DBに保存するときは内部情報も保持できるが、レスポンスには含めない
    stored = {"id": id_seq, **item.model_dump(), "tags": []}
    fake_db.append(stored)
    # response_modelにより、返却はItemOutに整形されます
    return stored
Shell
curl -s -X POST "http://127.0.0.1:8000/catalog/items" \
  -H "Content-Type: application/json" \
  -d '{"name":"Pen","price":120,"tax":0.1,"internal_note":"secret"}'
JSON
{"id":1,"name":"Pen","price":120.0,"tax":0.1,"tags":[]}

レスポンスモデルはそのAPIの契約であり、後方互換性を保つ助けになります。

ファイル構成とルータでコードを整理

規模が大きくなったら、APIRouterでエンドポイントを分割します。

機能単位(items, usersなど)で整理するのが定石です。

text
app/
├─ main.py
├─ routers/
│   ├─ items.py
│   └─ users.py
└─ schemas.py
Python
# app/schemas.py
from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(..., min_length=1)
    price: float = Field(..., ge=0)

class Item(BaseModel):
    id: int
    name: str
    price: float
Python
# app/routers/items.py
from fastapi import APIRouter, HTTPException
from ..schemas import ItemCreate, Item

router = APIRouter(prefix="/items", tags=["items"])

# 簡易インメモリDB
_db: list[Item] = []
_seq = 0

@router.post("", response_model=Item)
async def create_item(payload: ItemCreate):
    global _seq
    _seq += 1
    item = Item(id=_seq, name=payload.name, price=payload.price)
    _db.append(item)
    return item

@router.get("/{item_id}", response_model=Item)
async def get_item(item_id: int):
    for it in _db:
        if it.id == item_id:
            return it
    raise HTTPException(status_code=404, detail="Item not found")
Python
# app/routers/users.py
from fastapi import APIRouter

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

@router.get("")
async def list_users():
    return [{"id": 1, "name": "alice"}]
Python
# app/main.py
from fastapi import FastAPI
from .routers import items, users

app = FastAPI(title="Modular FastAPI")

# ルータを登録してURLを合体
app.include_router(items.router)
app.include_router(users.router)

# ルートは任意
@app.get("/")
async def root():
    return {"message": "OK"}
Shell
# ルータを含むアプリを起動
uvicorn app.main:app --port 8000

URLの共通prefixやタグでドキュメントも整理でき、大規模化しても見通しが良くなります

開発を速くする自動リロード(–reload)

開発時は--reloadを付けると、コード変更のたびにサーバが自動再起動します。

ミスにすぐ気づけ、反復速度が上がります。

Shell
uvicorn app.main:app --reload
実行結果
INFO:     Uvicorn running on http://127.0.0.1:8000
INFO:     Detected file change in 'app/routers/items.py'. Reloading...

本番運用では–reloadは使わない点に注意しましょう。

開発専用の便利機能です。

エラーハンドリングの基本とHTTP例外

見つからない・権限なしといった状況には、HTTPExceptionで適切なステータスコードを返します。

独自例外のハンドリングも可能です。

Python
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse

# 既存のルータ/アプリのどこかに追記

class OutOfStockError(Exception):
    def __init__(self, item_id: int):
        self.item_id = item_id

@app.get("/buy/{item_id}")
async def buy(item_id: int):
    # 在庫ゼロの例(実際はDBで判定)
    if item_id == 999:
        raise OutOfStockError(item_id)
    if item_id < 0:
        raise HTTPException(status_code=400, detail="Invalid item id")
    return {"status": "purchased", "item_id": item_id}

# 独自例外ハンドラ
@app.exception_handler(OutOfStockError)
async def out_of_stock_handler(request: Request, exc: OutOfStockError):
    return JSONResponse(
        status_code=409,  # 競合(CONFLICT)
        content={"error": "out_of_stock", "item_id": exc.item_id},
    )
Shell
# 404/400/409など適切なステータスが返る
curl -s -i "http://127.0.0.1:8000/buy/999" | head -n 20
実行結果
HTTP/1.1 409 Conflict
content-type: application/json
content-length: 37

{"error":"out_of_stock","item_id":999}

クライアントが対処しやすい形で失敗を設計することが、堅牢なAPIの鍵です。

HTTPステータスとエラー構造の一貫性を保ちましょう。

まとめ

FastAPIは高速・宣言的・自動ドキュメントという3拍子がそろった、Python初心者にも扱いやすいWeb APIフレームワークです。

最小構成で動かし、GET/POSTで基礎を押さえ、型ヒントとレスポンスモデルで契約を明確化し、ルータで整理すれば、小さく作ってスムーズに拡張できます。

最後に本番運用では–reloadを使わない適切なHTTPステータスで失敗を伝えるなどの基本も忘れずに、堅牢なAPI開発を進めてください。

OpenAPIドキュメント/docsを活用しつつ、まずは本稿のサンプルから手を動かしてみましょう。

Python 実践TIPS - Web・API開発
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!