💡
FastAPIでREST APIを構築し、curlとrequestsで動かしてみる
やること
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'}
コメントなど
普段の業務ではスルーしがちなところですが、この辺しっかり理解してアプリの設計とかできればいいなと思いました。
Discussion