0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPI入門

Posted at

FastAPI

FastAPIとは

  • 以下公式より引用
FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降でAPI を構築するための
モダンで、高速(高パフォーマンス)な、Web フレームワークです。

FastAPIの特徴

  • 直感的で分かりやすい書き方
    • Flaskに似ている
    • コードがシンプル
  • 公式ドキュメントの情報が豊富
    • 基礎を抑えれば、公式ドキュメントを参考にステップアップできる
    • 日本語訳も豊富
  • 自動ドキュメント生成機能がいい(docs/redoc)
  • バリデーション機能の実装が簡単
    • 入力データが規定や仕様に合っているか確認が可能
  • ハイパフォーマンスかつ他のフレームワークで標準掲載されていない機能を兼ね備えている
    • タイプヒントを用いたバリデーション
    • 非同期処理
    • オリジン間リソース共有(CORS)
    • GraphQL
    • WebSocket

Install

公式にインストール方法の記載があるので則る
https://fastapi.tiangolo.com/ja/#_3

📁 ~/work ❯ pip install fastapi
📁 ~/work ❯ pip install uvicorn
📁 ~/work ❯ pip list | grep -ie package -e fastapi -e uvicorn
Package            Version
fastapi            0.116.1
uvicorn            0.35.0

function

  • テスト用に以下のmain.pyを作成する
from fastapi import FastAPI

app = FastAPI()

@app.get("/") # appから始まる処理をデコレーターという下の部分も含む
async def index(): # async関数は非同期処理を行うためのもの
    return {"message": "Hello, World!"}  # レスポンスとしてJSON形式でメッセージを返す
  • サーバを起動する
    • Uvicornを利用
    • main:app main.pyの中のappを起動するという意味
    • --reload コードを変える度に自動的にリロードするという意味
📁 ~/work/fastapi ❯ uvicorn main:app --reload
INFO:     Will watch for changes in these directories: ['/Users/anzai/work/fastapi']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [3107] using StatReload
INFO:     Started server process [3138]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
  • 表示されたURLをブラウザで起動してmessageが表示されることを確認する

スクリーンショット 2025-08-11 14.10.30.png

仕組みとしてはブラウザアクセス(GETメソッド)によって関数が起動して
returnで定義しているmessage一式がブラウザに表示される流れとなる

Swagger UI

SwaggerはRESTful APIを構築するためのオープンソースのフレームワークのこと
OpenAPI Initiative(OAI)が定義したフォーマット
Swagger UIはSwagger-Specを読み込んで、HTML形式でドキュメントを生成するためのツール

例えば先ほど起動したURLに/docsを付けてアクセスするとAPIのドキュメントが作成されているのが分かる
http://127.0.0.1:8000/docs

Swaggerで定義された内容を元にhtmlベースのドキュメントを生成してくれるツール
http://127.0.0.1:8000/redoc

パスパラメータ

パスパラメータはAPIのパスで指定するパラメータのこと

main.pyを以下に変更する

  • 変更点 @app.get("/") → @app.get("/hello") に変更
from fastapi import FastAPI

app = FastAPI()

@app.get("/hello") # /helloにアクセス
async def index(): 
    return {"message": "Hello, World!"}  

スクリーンショット 2025-08-11 14.31.55.png

ちなみにmain.pyを変更したタイミングでuvicornは--reloadオプションを付与しているので
ログ上は以下の様に自動的にアプリが再起動される

WARNING:  StatReload detected changes in 'main.py'. Reloading...
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [3138]
INFO:     Started server process [3428]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

少し応用してみる

main.pyを以下に書き換えてみる

from fastapi import FastAPI

app = FastAPI()

@app.get("/countries/{country_name}") 
async def country(country_name): 
    return {"country_name": country_name}  

上記の場合パスパラメータから動的に変数を取り込んで、関数内の処理で利用することができる
結果としてアクセスするURLによってreturnを変えることができる
例)地域毎に処理を変えたい時、商品毎に処理を変えたい時

  • japan
    スクリーンショット 2025-08-11 14.35.26.png

  • china
    スクリーンショット 2025-08-11 14.36.10.png

  • india
    スクリーンショット 2025-08-11 14.37.10.png

もう少し応用して引数の値を制限することもできる
以下の場合はcoutry_nameをstringで固定する

from fastapi import FastAPI

app = FastAPI()

@app.get("/countries/{country_name}") 
async def country(country_name: str): 
    return {"country_name": country_name}  

この状態で再度docsにアクセスすると
自動的にこのAPIに関するドキュメントが作成されていることが分かる
http://127.0.0.1:8000/docs
スクリーンショット 2025-08-11 14.47.17.png

クエリパラメータ

URLの?以降に設定されるパラメータのこと
クエリーパラメータは複数設定することができる、その場合&で連結する
関数内でパスパラメータは無いが引数が設定されている場合、クエリパラメータになる

以下のmain.pyを作成する

from fastapi import FastAPI

app = FastAPI()

@app.get("/countries/") 
async def country(country_name: str = "japan", country_no: int = 1): 
    return {
        "country_name": country_name,
        "country_no": country_no
    }  

URLにアクセスする
まずはクエリパラメータなしのURLにアクセスする
http://127.0.0.1:8000/countries/
スクリーンショット 2025-08-11 15.48.14.png
この場合、関数でデフォルト値として設定している値がreturnとして表示されていることが分かる

逆にクエリパラメメータを明示的に指定してアクセスする
http://127.0.0.1:8000/countries/?country_name=china&country_no=2
スクリーンショット 2025-08-11 15.49.53.png
クエリパラメータで指定した値がreturnしていることが分かる

パスパラメータ&クエリパラメータの混合

パスパラメータとクエリパラメータを混ぜることもできる
例えば以下のコードを記述してみる

from fastapi import FastAPI

app = FastAPI()

@app.get("/countries/{country_name}") 
async def country(country_name: str = "japan", city_name: str = "tokyo"): 
    return {
        "country_name": country_name,
        "city_name": city_name
    }  

URLにアクセスするとパスパラメータjapanの場合、自動的にデフォルトのクエリパラメータが設定されることが分かる
http://127.0.0.1:8000/countries/japan
スクリーンショット 2025-08-11 15.59.10.png

明示的に以下の様にcountry_name,city_nameを指定する
http://127.0.0.1:8000/countries/china?city_name=Beijing

パスパラメータとクエリパラメータで指定した値がreturnされる事が分かる
スクリーンショット 2025-08-11 16.01.40.png

オプションパラメータ

main.pyを以下の様に変えてみる

  • 先頭行にOptionalモジュールのimportを追記
  • クエリパラメターの値country_name,country_noをOptionとして設定
from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/countries/") 
async def country(country_name: Optional[str] = None, country_no: Optional[int] = None): 
    return {
        "country_name": country_name,
        "country_no": country_no
    }  

クエリパラメータなしのURLにアクセスするとnullとなる
http://127.0.0.1:8000/countries/
スクリーンショット 2025-08-11 16.15.16.png

リクエストボディ

クライアントからAPIへのリクエストを送る際のデータをリクエストボディという
そのリクエストに対してAPIがクライアントに返す内容をレスポンスボディという

以下のmain.pyを作成する

  • BaseModelモジュールのimport
  • Item classの定義(データの型を定義)
  • postリクエストで取得した値を返す
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: int
    tax: Optional[float] = None

app = FastAPI()

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

この状態でdocsにアクセスすると、どの様な値をpostすれば良いのか確認できる
http://127.0.0.1:8000/docs
スクリーンショット 2025-08-11 16.35.45.png

Try it outをするとリクエストとレスポンスも確認できる
スクリーンショット 2025-08-11 16.39.06.png

例えば、POSTされたデータの中から特定の値を抜き出すこともできる

main.pyのreturnを以下に変えてみる

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: int
    tax: Optional[float] = None

app = FastAPI()

@app.post("/item/")
async def create_item(item: Item):
    return {"message": f"{item.name}は,税込価格{int(item.price*item.tax)}円です。"}

Try it outで試すと、特定の値を戻り値で抽出できていることが分かる
スクリーンショット 2025-08-11 16.46.07.png

PythonでAPIを叩いてみる

APIを叩くためのモジュールを作成していく

call_api.py

  • requests,jsonのモジュールをimport
  • post先のurl,bodyを設定し、urlにjson整形したbodyをpost
  • 返り値をjsonでprint
import requests
import json

def main():
    url = "http://127.0.0.1:8000/item/" # API endpoint
    body = {
        "name": "spinach",
        "description": "green vegetables",
        "price": 400,
        "tax": 1.1
    }
    res = requests.post(url, json.dumps(body))
    print(res.json())

if __name__ == "__main__":
    main()

実行結果

📁 ~/work/fastapi ❯ python call_api.py
{'message': 'spinachは,税込価格440円です。'}

リクエストボディを入れ子にする

main.py

  • List moduleも追加
  • classでデータ構造を複数定義
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel

class ShopInfo(BaseModel):
    name: str
    location: str

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: int
    tax: Optional[float] = None

class Data(BaseModel):
    shop_info: Optional[ShopInfo] = None
    items: List[Item]

app = FastAPI()

@app.post("/")
async def index(data: Data):
    return {"data": data} 

try it outしてみる
POSTしたデータ

{
  "shop_info": {
    "name": "test market",
    "location": "tokyo"
  },
  "items": [
    {
      "name": "apple",
      "description": "fruit",
      "price": 50,
      "tax": 1.1
    },
    {
      "name": "carrot",
      "description": "vegetable",
      "price": 20,
      "tax": 1.1
    }
  ]
}

スクリーンショット 2025-08-11 17.26.26.png

ポイントはデータ構造を定義していれば入れ子でも対応できる

バリデーション

FastAPIにおけるバリデーションとはデータの検証やチェックのこと

main.pyに以下の変更を加える

  • Fieldモジュールをインポート
  • nameの最小文字数、最大文字数を設定
from typing import Optional, List
from fastapi import FastAPI
from pydantic import BaseModel, Field

class ShopInfo(BaseModel):
    name: str 
    location: str

class Item(BaseModel):
    name: str = Field(min_length=4, max_length=12) # 最小文字数4, 最大文字数12
    description: Optional[str] = None
    price: int
    tax: Optional[float] = None

class Data(BaseModel):
    shop_info: Optional[ShopInfo] = None
    items: List[Item]

app = FastAPI()

@app.post("/")
async def index(data: Data):
    return {"data": data} 

docs上でもitemのnameに制限が加わったことを確認できる
スクリーンショット 2025-08-11 21.05.33.png

では実際にテストとして最小文字数を満たさない文字数でテストしてみる
スクリーンショット 2025-08-11 21.06.57.png
少なくとも4文字にする様に422errorが出ていることが確認できる

では条件を満たす4文字でリクエストを送ってみる
スクリーンショット 2025-08-11 21.08.29.png
今度はステータスコードが200で問題ないことが確認できる

では最大文字数を超えてみる
スクリーンショット 2025-08-11 21.10.40.png
12文字にする様に怒られた

以上

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?