FastAPIでCRUD APIを作る入門チュートリアル

概要

FastAPIは、Pythonで高性能なAPIを構築するためのWebフレームワークです。以下の特徴があります。

  • 型ヒントベース: Pythonの型ヒントを活用し、リクエスト・レスポンスの自動バリデーションを実現
  • 自動ドキュメント: Swagger UI(OpenAPI)が自動生成され、ブラウザからAPIをテスト可能
  • 高速: Starlette + Pydanticをベースにしており、Node.jsやGoに匹敵するパフォーマンス

この記事では、FastAPIを使ってTODOアプリのCRUD APIを作成する手順を解説します。

前提条件

環境構築

まず、プロジェクトを作成し、必要なパッケージをインストールします。

1
2
3
uv init fastapi-todo
cd fastapi-todo
uv add fastapi uvicorn

fastapiがWebフレームワーク本体、uvicornがASGIサーバーです。

Hello World

まずは最小構成でFastAPIの動作を確認します。main.pyを作成してください。

1
2
3
4
5
6
7
8
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

サーバーを起動します。

1
uv run uvicorn main:app --reload

--reloadオプションを付けると、コードの変更時に自動でリロードされます。

ブラウザで http://localhost:8000 にアクセスすると、以下のレスポンスが返ります。

1
{"message": "Hello, FastAPI!"}

Swagger UIの確認

FastAPIの大きな魅力は、自動生成されるAPIドキュメントです。http://localhost:8000/docs にアクセスすると、Swagger UIが表示されます。

ここからAPIを直接テストできるため、開発中はcurlやPostmanなしでも動作確認が可能です。

CRUD APIの実装

TODOアプリのCRUD APIを実装していきます。

モデルの定義

まず、PydanticモデルでTODOのデータ構造を定義します。main.pyを以下のように書き換えてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel


app = FastAPI()


class TodoCreate(BaseModel):
    title: str
    completed: bool = False


class TodoResponse(BaseModel):
    id: int
    title: str
    completed: bool


todos: dict[int, dict] = {}
next_id: int = 1
  • TodoCreate: 新規作成・更新時のリクエストボディ
  • TodoResponse: レスポンスの型定義
  • todos: インメモリのデータストア(dict)
  • next_id: IDの自動採番用カウンター

作成(POST)

1
2
3
4
5
6
7
@app.post("/todos", response_model=TodoResponse)
def create_todo(todo: TodoCreate):
    global next_id
    todo_data = {"id": next_id, "title": todo.title, "completed": todo.completed}
    todos[next_id] = todo_data
    next_id += 1
    return todo_data

response_model=TodoResponseを指定することで、レスポンスの型がSwagger UIに反映されます。

一覧取得(GET)

1
2
3
@app.get("/todos", response_model=list[TodoResponse])
def list_todos():
    return list(todos.values())

個別取得(GET)

1
2
3
4
5
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todos[todo_id]

存在しないIDの場合はHTTPExceptionで404エラーを返します。

更新(PUT)

1
2
3
4
5
6
7
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo_data = {"id": todo_id, "title": todo.title, "completed": todo.completed}
    todos[todo_id] = todo_data
    return todo_data

削除(DELETE)

1
2
3
4
5
6
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    del todos[todo_id]
    return {"detail": "Todo deleted"}

完成したコード

上記をすべてまとめたmain.pyの全体像は以下のとおりです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel


app = FastAPI()


class TodoCreate(BaseModel):
    title: str
    completed: bool = False


class TodoResponse(BaseModel):
    id: int
    title: str
    completed: bool


todos: dict[int, dict] = {}
next_id: int = 1


@app.post("/todos", response_model=TodoResponse)
def create_todo(todo: TodoCreate):
    global next_id
    todo_data = {"id": next_id, "title": todo.title, "completed": todo.completed}
    todos[next_id] = todo_data
    next_id += 1
    return todo_data


@app.get("/todos", response_model=list[TodoResponse])
def list_todos():
    return list(todos.values())


@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todos[todo_id]


@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo_data = {"id": todo_id, "title": todo.title, "completed": todo.completed}
    todos[todo_id] = todo_data
    return todo_data


@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    del todos[todo_id]
    return {"detail": "Todo deleted"}

動作確認

サーバーを起動して、Swagger UIから動作確認を行います。

1
uv run uvicorn main:app --reload

http://localhost:8000/docs にアクセスし、以下の順にテストしてください。

1. TODOの作成

POST /todosを開き、「Try it out」をクリックして以下のJSONを入力します。

1
2
3
4
{
  "title": "買い物に行く",
  "completed": false
}

「Execute」をクリックすると、以下のようなレスポンスが返ります。

1
2
3
4
5
{
  "id": 1,
  "title": "買い物に行く",
  "completed": false
}

2. 一覧取得

GET /todosを実行すると、作成したTODOの一覧が返ります。

3. 更新

PUT /todos/1completedtrueに変更してみましょう。

1
2
3
4
{
  "title": "買い物に行く",
  "completed": true
}

4. 削除

DELETE /todos/1を実行すると、TODOが削除されます。GET /todosで空の配列が返ることを確認してください。

まとめ

この記事では、FastAPIを使ってTODO管理のCRUD APIを作成しました。FastAPIの主な利点をまとめます。

  • Pydanticモデルによるリクエスト・レスポンスの自動バリデーション
  • Swagger UIによるインタラクティブなAPIドキュメント
  • Pythonの型ヒントだけでAPIの仕様が定義できるシンプルさ

今回はインメモリのデータストアを使いましたが、実際のアプリケーションではSQLAlchemyなどのORMを使ってデータベースと連携することになります。

関連記事