L11 ch03 — POST リクエスト
テクノロジ系 / L11 fastapi-basic — FastAPI 入門
L11 ch03 — POST リクエスト
クライアントから JSON を受け取る
ゴール
@app.post()で POST エンドポイント- リクエストボディの取得
- ステータスコード制御 (201 Created)
Request 経由 (基本)
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/tasks", status_code=201)
async def create_task(request: Request):
body = await request.json()
return {
"title": body["title"],
"done": body.get("done", False),
}→ async def + await が必須
動作確認 (curl)
curl -X POST http://localhost:8000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "買い物"}'
# {"title": "買い物", "done": false}Swagger UI で操作
http://localhost:8000/docs で POST エンドポイントを 画面から実行可能。
- パラメータ入力欄
- Try it out → Execute
- レスポンス確認
ステータスコード
| Code | 意味 | 用途 |
|---|---|---|
| 200 | OK | GET 成功 |
| 201 | Created | POST で新規作成 |
| 204 | No Content | DELETE 成功 (body 無し) |
| 400 | Bad Request | クライアント不正 |
| 422 | Unprocessable Entity | 型違反 |
| 500 | Internal Server Error | サーバー側エラー |
デフォルト値
new_task = {
"title": body["title"],
"done": body.get("done", False), # 省略時 False
}dict.get(key, default) で キー欠落 + デフォルト を一発処理。
Pydantic との接続
L12 で Pydantic モデルを使うと 型安全 + 自動バリデーションに進化。
class TaskIn(BaseModel):
title: str
done: bool = False
@app.post("/tasks")
def create(task: TaskIn): # ← この型で自動検証
return taskBaseModel を使う完全形(推奨パターン)
Request.json() ではなく、Pydantic BaseModel を引数の型ヒントに置くのが FastAPI の本来の使い方。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class EchoPayload(BaseModel):
text: str
repeat: int = 1 # デフォルト値あり = 省略可能
@app.post("/echo", status_code=201)
def echo(payload: EchoPayload): # ← async ではなく普通の def でOK
return {
"echoed": payload.text * payload.repeat,
}curl -X POST http://localhost:8000/echo \
-H "Content-Type: application/json" \
-d '{"text": "hi", "repeat": 3}'
# {"echoed": "hihihi"}- 引数の型に
BaseModelを書くだけで JSON → Python オブジェクトに自動変換 payload.textのように属性アクセスできる (dict のbody["text"]よりタイポに強い)text欠落 /repeatに文字列を入れる → 自動で 422 + 詳細なエラー JSONasync def/await request.json()不要 → コードが短くなる
> 今後 POST / PATCH を書くときは 基本このパターン。Request.json() は「型を決められない / 動的なキー」のときだけ。
drill
drill/ — POST + status_code=201
ハンズオン
ch03-post 演習ガイド
受講生向け演習問題集
採点の仕組み (読んでから始めてください)
採点テストは 「お題の通りに動くか」 を機械的にチェックします。
ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。
タイプ fill (穴埋め問題) — 文法・APIを知っているか
___(3個並んだアンダースコア) の空欄を埋める- 既存の構造は そのまま
- ロジック自由度: 低 (お題どおりに直す)
タイプ add (機能追加問題) — 既存コードに機能を足せるか
- 既存コードを残したまま、新しいエンドポイント/機能を 追加 する
- 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
- ただしテストが要求する I/F (URL・HTTPメソッド・レスポンスのキー名) は守る
- 既存テストを壊さないこと
タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか
- ほぼ空の
main.pyから 自力で全部書く - ファイル構造・関数分割・命名・内部ロジック: 完全に自由
- 守るのはインターフェースだけ: URL・HTTP メソッド・レスポンス JSON 形式
- 一番ハードだが、実装力が一番つくタイプ
3タイプ共通で守ること
- ファイル名 (
main.py) は変えない tests/フォルダは触らない (採点ロジック)
このガイドのドリル一覧
| # | ドリル名 | タイトル | タイプ | 難易度 |
|---|---|---|---|---|
| 1 | 01-fill-request-body | @app.post + Request.json() - リクエストボディ受け取り | 🔧 穴埋め | ★ |
| 2 | 02-build-user-api | パスパラメータ + POST + HTTPException - User API を白紙から | 🛠️ ゼロ実装 | ★★ |
> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。
ドリル 1: @app.post + Request.json() - リクエストボディ受け取り
ドリル名: 01-fill-request-body
> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。
___ を埋めて、POST エンドポイントを完成させてください。
Requestを使ってボディを受け取るdoneが省略された場合はFalseをデフォルトにする- ステータスコードは 201 を返す
from fastapi import FastAPI, ___
app = FastAPI()
@app.post("/tasks", status_code=___)
async def create_task(request: ___):
body = await request.___()
new_task = {
"title": body["title"],
"done": body.___( "done", False),
}
return new_taskドリル 1: ヒント
ドリル名: 01-fill-request-body
> ⚠️ 自力で考えてから読んでください
from fastapi import FastAPI, Request—Requestもインポートするstatus_code=201— 作成成功のステータスコードasync def+await request.json()— ボディを dict として受け取るbody.get("done", False)— キーがない場合のデフォルト値を指定する
ドリル 2: パスパラメータ + POST + HTTPException - User API を白紙から
ドリル名: 02-build-user-api
> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の main.py から仕様を読んで自力で実装してください。ファイル構造・関数名・内部ロジックは完全に自由。ただしテストが叩く インターフェース (URL / HTTP メソッド / レスポンス形式) は仕様どおりに揃えること。
問題
ユーザー情報を返す API を main.py に 白紙から 実装してください。
パスパラメータと POST ボディの両方を扱う総合問題です。
仕様
ユーザーデータはコード内で固定 (DB 不要):
USERS = [
{"id": 1, "name": "Alice", "age": 30},
{"id": 2, "name": "Bob", "age": 25},
]| メソッド | URL | リクエスト | レスポンス |
|---|---|---|---|
| GET | /users | - | USERS の配列 |
| GET | /users/{user_id} | (パスパラメータ) | 該当ユーザー (存在しなければ 404) |
| POST | /echo | {"text": "..."} | {"echoed": "..."} (text をそのまま echoed に入れる) |
守る (テストが見ている)
user_idはint型として受け取る- 存在しない user_id への GET は 404
- POST /echo は JSON ボディの
textをechoedキーで返す
自由に設計できる例
- ユーザー検索ロジック (for / dict / next() 等)
- Pydantic を使う / 使わない (POST ボディ受け取り)
このドリルで学ぶこと
| 学習要素 | 学習資料との対応 |
|---|---|
| GET一覧 + GETパスパラメータ + POSTボディ を1つの API に統合 | ch02 + ch03 の総合 |
パスパラメータの型ヒント (user_id: int) | ch02 slide「仕組み」 |
HTTPException(status_code=404) で「見つからない」を表現 | ch02 slide「見つからない時の 404」 |
Pydantic BaseModel でリクエストボディを受け取る | ch03 slide「BaseModel を使う完全形」 |
このドリルは ch02 と ch03 を横断する総合問題。複数の概念を1ファイルに同居させる「設計の統合」を体験します。
ドリル 2: ヒント
ドリル名: 02-build-user-api
> ⚠️ 自力で考えてから読んでください
GET /users
USERS をそのまま return するだけ。
GET /users/{user_id}
def get_user(user_id: int):で型ヒントを付ける- USERS をループして該当ユーザーを探す
- 見つからなければ
raise HTTPException(status_code=404)
POST /echo
- Pydantic
BaseModelでtext: strを持つクラスを定義 def echo(payload: EchoPayload):で受け取り{"echoed": payload.text}を返す
考えてみよう
既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。
ch03-post 演習ガイド
受講生向け演習問題集
採点の仕組み (読んでから始めてください)
採点テストは 「お題の通りに動くか」 を機械的にチェックします。
ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。
タイプ fill (穴埋め問題) — 文法・APIを知っているか
___(3個並んだアンダースコア) の空欄を埋める- 既存の構造は そのまま
- ロジック自由度: 低 (お題どおりに直す)
タイプ add (機能追加問題) — 既存コードに機能を足せるか
- 既存コードを残したまま、新しいエンドポイント/機能を 追加 する
- 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
- ただしテストが要求する I/F (URL・HTTPメソッド・レスポンスのキー名) は守る
- 既存テストを壊さないこと
タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか
- ほぼ空の
main.pyから 自力で全部書く - ファイル構造・関数分割・命名・内部ロジック: 完全に自由
- 守るのはインターフェースだけ: URL・HTTP メソッド・レスポンス JSON 形式
- 一番ハードだが、実装力が一番つくタイプ
3タイプ共通で守ること
- ファイル名 (
main.py) は変えない tests/フォルダは触らない (採点ロジック)
このガイドのドリル一覧
| # | ドリル名 | タイトル | タイプ | 難易度 |
|---|---|---|---|---|
| 1 | 01-fill-request-body | @app.post + Request.json() - リクエストボディ受け取り | 🔧 穴埋め | ★ |
| 2 | 02-build-user-api | パスパラメータ + POST + HTTPException - User API を白紙から | 🛠️ ゼロ実装 | ★★ |
> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。
ドリル 1: @app.post + Request.json() - リクエストボディ受け取り
ドリル名: 01-fill-request-body
> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。
___ を埋めて、POST エンドポイントを完成させてください。
Requestを使ってボディを受け取るdoneが省略された場合はFalseをデフォルトにする- ステータスコードは 201 を返す
from fastapi import FastAPI, ___
app = FastAPI()
@app.post("/tasks", status_code=___)
async def create_task(request: ___):
body = await request.___()
new_task = {
"title": body["title"],
"done": body.___( "done", False),
}
return new_taskドリル 1: ヒント
ドリル名: 01-fill-request-body
> ⚠️ 自力で考えてから読んでください
from fastapi import FastAPI, Request—Requestもインポートするstatus_code=201— 作成成功のステータスコードasync def+await request.json()— ボディを dict として受け取るbody.get("done", False)— キーがない場合のデフォルト値を指定する
ドリル 2: パスパラメータ + POST + HTTPException - User API を白紙から
ドリル名: 02-build-user-api
> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の main.py から仕様を読んで自力で実装してください。ファイル構造・関数名・内部ロジックは完全に自由。ただしテストが叩く インターフェース (URL / HTTP メソッド / レスポンス形式) は仕様どおりに揃えること。
問題
ユーザー情報を返す API を main.py に 白紙から 実装してください。
パスパラメータと POST ボディの両方を扱う総合問題です。
仕様
ユーザーデータはコード内で固定 (DB 不要):
USERS = [
{"id": 1, "name": "Alice", "age": 30},
{"id": 2, "name": "Bob", "age": 25},
]| メソッド | URL | リクエスト | レスポンス |
|---|---|---|---|
| GET | /users | - | USERS の配列 |
| GET | /users/{user_id} | (パスパラメータ) | 該当ユーザー (存在しなければ 404) |
| POST | /echo | {"text": "..."} | {"echoed": "..."} (text をそのまま echoed に入れる) |
守る (テストが見ている)
user_idはint型として受け取る- 存在しない user_id への GET は 404
- POST /echo は JSON ボディの
textをechoedキーで返す
自由に設計できる例
- ユーザー検索ロジック (for / dict / next() 等)
- Pydantic を使う / 使わない (POST ボディ受け取り)
このドリルで学ぶこと
| 学習要素 | 学習資料との対応 |
|---|---|
| GET一覧 + GETパスパラメータ + POSTボディ を1つの API に統合 | ch02 + ch03 の総合 |
パスパラメータの型ヒント (user_id: int) | ch02 slide「仕組み」 |
HTTPException(status_code=404) で「見つからない」を表現 | ch02 slide「見つからない時の 404」 |
Pydantic BaseModel でリクエストボディを受け取る | ch03 slide「BaseModel を使う完全形」 |
このドリルは ch02 と ch03 を横断する総合問題。複数の概念を1ファイルに同居させる「設計の統合」を体験します。
ドリル 2: ヒント
ドリル名: 02-build-user-api
> ⚠️ 自力で考えてから読んでください
GET /users
USERS をそのまま return するだけ。
GET /users/{user_id}
def get_user(user_id: int):で型ヒントを付ける- USERS をループして該当ユーザーを探す
- 見つからなければ
raise HTTPException(status_code=404)
POST /echo
- Pydantic
BaseModelでtext: strを持つクラスを定義 def echo(payload: EchoPayload):で受け取り{"echoed": payload.text}を返す
ch03-post 演習解説
⚠️ 自力でドリルを解いてから読んでください
ドリル 1: @app.post + Request.json() - リクエストボディ受け取り — 模範解答
ドリル名: 01-fill-request-body
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/tasks", status_code=201)
async def create_task(request: Request):
body = await request.json()
new_task = {
"title": body["title"],
"done": body.get("done", False),
}
return new_taskドリル 1: @app.post + Request.json() - リクエストボディ受け取り — 解説
ドリル名: 01-fill-request-body
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/tasks", status_code=201)
async def create_task(request: Request):
body = await request.json()
new_task = {
"title": body["title"],
"done": body.get("done", False),
}
return new_task穴埋め一覧
| 穴 | 答え | 理由 |
|---|---|---|
import ___ | Request | リクエストオブジェクトを受け取るクラス |
status_code=___ | 201 | 作成成功を表すHTTPステータス |
request: ___ | Request | 引数の型アノテーション |
request.___() | json | ボディをdictとして受け取るメソッド |
body.___(...) | get | キーがない場合のデフォルト値を返すdictメソッド |
ドリル 2: パスパラメータ + POST + HTTPException - User API を白紙から — 模範解答
ドリル名: 02-build-user-api
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
USERS = [
{"id": 1, "name": "Alice", "age": 30},
{"id": 2, "name": "Bob", "age": 25},
]
class EchoPayload(BaseModel):
text: str
@app.get("/users")
def list_users():
return USERS
@app.get("/users/{user_id}")
def get_user(user_id: int):
for u in USERS:
if u["id"] == user_id:
return u
raise HTTPException(status_code=404, detail="user not found")
@app.post("/echo")
def echo(payload: EchoPayload):
return {"echoed": payload.text}ドリル 2: パスパラメータ + POST + HTTPException - User API を白紙から — 解説
ドリル名: 02-build-user-api
ch02-path-params + ch03-post の総合問題。
ポイント
- 型ヒント (
user_id: int) を付けると FastAPI が自動で int に変換、できなければ 422 を返す - Pydantic BaseModel で POST ボディを受け取ると、JSON Schema が自動生成 + バリデーション
- 404 を返す には
HTTPException(status_code=404)をraiseする
実装が複数あり得る点
- ユーザー検索:
forループ /next((u for u in USERS if u["id"] == id), None)/dict[int, dict]に変換しておく - 見つからない時の
detailメッセージ: 自由 (テストは status_code しか見ていない)