@PostMappingとリクエストボディ
このレッスンでは、@PostMapping と @RequestBody を使って、新しいリソースを作るエンドポイントを設計できるようになります。
POST は集合に対する操作
GET / PUT / DELETE が 1 件を指すのに対し、POST だけは集合に向けて送ります。
プレーンテキスト
POST /users/users/12 ではなく /users に送るのは、まだ ID が決まっていないからです。「この集合に、こういう内容の要素を 1 つ追加してほしい」という依頼が POST です。ID を決めるのはサーバーの仕事で、決まった ID はレスポンスで教えます。
@RequestBody で本文を受け取る
GET と違い、POST はリクエスト本文を持ちます。JSON で送られてきた本文をオブジェクトに変換して受け取るのが @RequestBody です。
Java
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public UserResponse create(@RequestBody CreateUserRequest request) {
User saved = service.create(request.name(), request.email());
return UserResponse.from(saved);
}
}{"name":"sato","email":"sato@example.com"} という本文が送られてくると、Jackson が CreateUserRequest のプロパティに値を詰めてくれます。この変換は @ResponseBody の逆向きの処理で、同じ HttpMessageConverter の仕組みが担当します。
@RequestParam との違い
| アノテーション | 読む場所 | 主な用途 |
|---|---|---|
@PathVariable | URL のパス | どのリソースかの指定 |
@RequestParam | クエリ文字列 または フォーム | 絞り込み・簡単な値 |
@RequestBody | リクエスト本文 | 構造のあるデータの送信 |
新規作成のように項目が多く入れ子もあるデータは、本文で送るのが自然です。クエリ文字列に長い JSON を詰めるようなことはしません。
送られてきた内容は必ず疑う
ここが最も大事な点です。リクエスト本文はクライアントが自由に作れるので、名前が空だったり、メールの形式が壊れていたり、そもそもキーが無かったりします。変換に成功したことと、業務上正しいことは別です。
Spring では Bean Validation と @Valid を組み合わせて検証を宣言的に書けます。
Java
public record CreateUserRequest(
@NotBlank String name,
@NotBlank @Email String email) {
}
@PostMapping
public UserResponse create(@Valid @RequestBody CreateUserRequest request) { ... }@Valid を付けると、メソッドの中身に入る前に検証が走り、違反があればハンドラは呼ばれずに 400 が返ります。検証の詳細は後のセクションで扱いますが、まずは「本文を受け取ったら必ず検証してから使う」という順番を身につけてください。
作成に成功したときに返すもの
作成が成功したら、次の 3 つを返すのが定石です。
- ステータスコード 201
- 作られたリソースの場所を示す Location ヘッダ
- 本文として、採番された ID を含む作成結果
クライアントは ID を知らずに POST しているので、ID を返してあげないと次の操作ができません。この組み立て方は、このセクションの最後のレッスンで扱います。
演習で確かめます
Spring では @RequestBody が JSON をオブジェクトに変換し、@Valid が入力の検証を引き受けます。ここではその両方を自分で書きます。名前とメールを受け取り、名前が空でなければ採番した ID を付けた作成結果を返し、名前が空なら invalid を返す関数を作ってください。
要件
- name が null または空白のみなら invalid を返すこと
- name の前後の空白は取り除いてから使うこと
- 妥当な場合は id=13,name=sato,email=sato@example.com の形で返し、id は currentCount + 1 とすること
入出力例
createUser("sato", "sato@example.com", 12) → "id=13,name=sato,email=sato@example.com"
createUser(" taro ", "taro@example.com", 0) → "id=1,name=taro,email=taro@example.com"
createUser("", "a@example.com", 5) → "invalid"
createUser(" ", "a@example.com", 5) → "invalid"
createUser("hanako", "hanako@example.com", 99) → "id=100,name=hanako,email=hanako@example.com"