💡

FastAPIでREST APIを構築し、curlとrequestsで動かしてみる

2024/12/18に公開

やること

REST APIの概念を理解し、動かしてみる

REST APIとは?

RESTの原則に従って、設計・実装されたAPIのことです。RESTは以下6つを組み合わせたアーキテクチャスタイルのことを指します[1]

・クライアント/サーバ
・ステートレスサーバ
・キャッシュ
・統一インターフェース
・階層化システム
・コードオンデマンド

REST APIは上記の特徴からシンプルで使いやすい設計が求められるシステムやサービスで広く利用されており、特に柔軟性や拡張性に優れているため、マイクロサービスやWebアプリケーションにおける標準的なAPI設計手法となっています。

REST APIの設計・実装

今回は忘年会シーズンなので居酒屋のmenuを対象リソースとし、以下の仕様に基づいてコードを実装してみます。

操作 URI HTTPメソッド 説明
メニュー一覧を取得する /menus GET すべてのメニュー情報を取得する
新しいメニューを追加する /menus POST 新しいメニュー情報を追加する
特定のメニュー情報を取得する /menus/{id} GET IDで指定されたメニュー情報を取得する
特定のメニュー情報を更新する /menus/{id} PUT IDで指定されたメニュー情報を更新する
特定のメニュー情報を削除する /menus/{id} DELETE IDで指定されたメニュー情報を削除する
スクリプト
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI()

menus = [
    {"id": 1, "name": "枝豆", "price": 300},
    {"id": 2, "name": "唐揚げ", "price": 500},
    {"id": 3, "name": "生ビール", "price": 600},
    {"id": 4, "name": "焼き鳥盛り合わせ", "price": 800},
    {"id": 5, "name": "刺身盛り合わせ", "price": 1200},
    {"id": 6, "name": "モツ鍋", "price": 1500},
    {"id": 7, "name": "おでん盛り合わせ", "price": 700},
    {"id": 8, "name": "牛すじ煮込み", "price": 650},
    {"id": 9, "name": "日本酒(冷・熱燗)", "price": 900},
    {"id": 10, "name": "シメのうどん", "price": 400},
]

class Menu(BaseModel):
    name: str
    price: int
    
class MenuUpdate(BaseModel):
    name: str = None
    price: int = None

# 1. メニュー一覧を取得する
@app.get("/menus", response_model = List[dict])
def get_menus():
    return menus

# 2. 新しいメニューを追加する
@app.post("/menus", response_model = dict, status_code = 201)
def add_menu(menu: Menu):
    new_menu = {"id": len(menus) + 1, "name": menu.name, "price": menu.price}
    menus.append(new_menu)
    return new_menu

# 3. 特定のメニュー情報を取得する
@app.get("/menus/{id}", response_model = dict)
def get_menu(id: int):
    menu = next((m for m in menus if m["id"] == id), None)
    if not menu:
        raise HTTPException(status_code = 404, detail = "Menu not found")
    return menu

# 4. 特定のメニュー情報を更新する
@app.put("/menus/{id}", response_model = dict)
def update_menu(id: int, updated_menu: MenuUpdate):
    menu = next((m for m in menus if m["id"] == id), None)
    if not menu:
        raise HTTPException(status_code = 404, detail = "Menu not found")
    
    if updated_menu.name is not None:
        menu["name"] = updated_menu.name
    if updated_menu.price is not None:
        menu["price"] = updated_menu.price
    
    return menu

# 5. 特定のメニュー情報を削除する
@app.delete("/menus/{id}", response_model = dict)
def delete_menu(id: int):
    global menus
    menu = next((m for m in menus if m["id"] == id), None)
    if not menu:
        raise HTTPException(status_code = 404, detail = "Menu not found")
    menus = [m for m in menus if m["id"] != id]
    return{"message": "Menu deleted"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000, reload=True)

APIを実行してみる

uvicorn menu_api:app --reloadでFastAPIサーバーを起動し、http://127.0.0.1:8000/docsでswaggerUIが無事に表示されたため、以下bashコマンドでAPIを実行してみます。

1. メニュー一覧を取得する

curl -X GET http://127.0.0.1:8000/menus
[
{"id":1,"name":"枝豆","price":300},
{"id":2,"name":"唐揚げ","price":500},
{"id":3,"name":"生ビール","price":600},
{"id":4,"name":"焼き鳥盛り合せ","price":800},
{"id":5,"name":"刺身盛り合せ","price":1200},
{"id":6,"name":"モツ鍋","price":1500},
{"id":7,"name":"おでん盛り合せ","price":700},
{"id":8,"name":"牛すじ煮込み","price":650},
{"id": 9, "name": "日本酒(冷・熱燗)", "price": 900},
{"id": 10, "name": "シメのうどん", "price": 400}
]

2. 新しいメニューを追加する

add_menu.json
{
    "name": "海鮮丼",
    "price": 1000
  }

add_menu.jsonというデータを用意しておき、以下のコマンドを実行しました。

curl -X POST "http://127.0.0.1:8000/menus" \
-H "Content-Type: application/json" --data @add_menu.json
{"id":11,"name":"海鮮丼","price":1000}

3. 特定のメニュー情報を取得する

curl -X GET http://127.0.0.1:8000/menus/5
{"id":5,"name":"刺身盛り合わせ","price":1200}

4. 特定のメニュー情報を更新する

curl -X PUT http://127.0.0.1:8000/menus/5 \
-H "Content-Type: application/json" \
-d '{"price": 2000}'
{"id":5,"name":"刺身盛り合わせ","price":2000}

5. 特定のメニュー情報を削除する

curl -X DELETE http://127.0.0.1:8000/menus/10
{"message":"Menu deleted"}

requestsで使ってみる

手軽にAPIの動作確認するだけならコマンドラインでやればいいんですが、Pythonコード内でAPIを操作したいときはrequestsライブラリを使います。

スクリプト
import requests

BASE_URL = "http://127.0.0.1:8000/menus"

# 1. メニュー一覧を取得する
response = requests.get(BASE_URL)
print("1. メニュー一覧を取得する:")
print(response.json())

# 2. 新しいメニューを追加する
print("\n2. 新しいメニューを追加する:")
new_menu = {"name": "海鮮丼", "price": 1000}
response = requests.post(BASE_URL, json=new_menu)
print(response.json())

# 3. 特定のメニュー情報を取得する
print("\n3. 特定のメニュー情報を取得する (ID: 5):")
menu_id = 5
response = requests.get(f"{BASE_URL}/{menu_id}")
print(response.json())

# 4. メニュー情報を更新する
print("\n4. メニュー情報を更新する (ID: 5):")
updated_menu = {"price": 2000}
response = requests.put(f"{BASE_URL}/{menu_id}", json=updated_menu)
print(response.json())

# 5. メニュー情報を削除する
print("\n5. メニュー情報を削除する (ID: 10):")
delete_id = 10
response = requests.delete(f"{BASE_URL}/{delete_id}")
print(response.json())
1. メニュー一覧を取得する:
[{'id': 1, 'name': '枝豆', 'price': 300}, {'id': 2, 'name': '唐揚げ', 'price': 500}, {'id': 3, 'name': '生ビール', 'price': 600}, {'id': 4, 'name': '焼き鳥盛り合わせ', 'price': 800}, {'id': 5, 'name': '刺身盛り合わせ', 'price': 1200}, {'id': 6, 'name': 'モツ鍋', 'price': 1500}, {'id': 7, 'name': 'おでん 盛り合わせ', 'price': 700}, {'id': 8, 'name': '牛すじ煮込み', 'price': 650}, {'id': 9, 'name': '日本酒(冷・熱燗)', 'price': 900}, {'id': 10, 'name': 'シメのうどん', 'price': 400}]  

2. 新しいメニューを追加する:
{'id': 11, 'name': '海鮮丼', 'price': 1000}

3. 特定のメニュー情報を取得する (ID: 5):
{'id': 5, 'name': '刺身盛り合わせ', 'price': 1200}

4. メニュー情報を更新する (ID: 5):
{'id': 5, 'name': '刺身盛り合わせ', 'price': 2000}

5. メニュー情報を削除する (ID: 10):
{'message': 'Menu deleted'}

コメントなど

普段の業務ではスルーしがちなところですが、この辺しっかり理解してアプリの設計とかできればいいなと思いました。

脚注
  1. https://gihyo.jp/book/2010/978-4-7741-4204-3 ↩︎

ヘッドウォータース

Discussion