REST APIクイックスタートガイド
REST APIはマイクロサービスの共通言語です。HTTP動詞でリソースを操作し, JSONでデータを転送することで, シンプル, 標準的, かつ効率的に通信できます。
1. 学ぶ内容
@RestController/@RequestMapping/@GetMapping/@PostMappingアノテーション体系- パス変数
@PathVariable, リクエストパラメータ@RequestParam, リクエストボディ@RequestBody - HTTPステータスコードと
ResponseEntityによるカスタムレスポンス - Postman / curlを使ったREST APIのテスト
- Aliceが注文の作成, 照会, 更新, 削除の4つの操作を実装
2. API開発者の実話
(1) ペインポイント:API仕様の不統一
Aliceは既存のプロジェクトチームに参加し, REST APIの設計が混乱していることを発見しました。一部のメソッドはGETでデータを変更し, URLの命名が不統一 (/getOrder, /order/list, /deleteOrderById)で, レスポンス形式も標準化されていませんでした。JSONを返すものもあればXMLを返すものもあり, エラーレスポンスはプレーンテキストでした。フロントエンド開発者のBobは, 新しいAPIを統合するたびにバックエンドチームに確認する必要があり, コミュニケーションに多大な時間を浪費していました。
(2) RESTfulアプローチ
RESTは統一された規約のセットでAPI設計を定義します。
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}") // GET /api/orders/123
@PostMapping // POST /api/orders
@PutMapping("/{id}") // PUT /api/orders/123
@DeleteMapping("/{id}") // DELETE /api/orders/123
}
(3) 成果
AliceがOrderFlowのAPIをRESTfulスタイルにリファクタリングした後, URLの命名規則が統一され, HTTP動詞のセマンティクスが明確になりました。フロントエンド開発者のBobは「URLを見るだけで使い方がわかる」と言い, API統合に要する時間は平均2時間から15分に短縮されました。
3. RESTの核心概念
(1) RESTの6つの原則
REST (Representational State Transfer)はこのアーキテクチャスタイルの核心的な制約を定義します。
graph TD
A[RESTの制約] --> B[クライアント・サーバー<br/>関心の分離]
A --> C[ステートレス<br/>各リクエストに<br/>必要な情報をすべて含む]
A --> D[キャッシュ可能<br/>レスポンスが<br/>キャッシュ性を定義]
A --> E[統一インターフェース<br/>一貫したAPI設計]
A --> F[階層化システム<br/>クライアントは直接接続か<br/>どうかを区別できない]
A --> G[オンデマンドコード<br/>オプション:サーバーが<br/>実行可能コードを送信可能]
(2) HTTP動詞とCRUD操作のマッピング
| HTTP動詞 | 操作 | べき等性 | 安全性 | 代表的なURL |
|---|---|---|---|---|
| GET | 照会 | あり | あり | /api/orders |
| POST | 作成 | なし | なし | /api/orders |
| PUT | 全体更新 | あり | なし | /api/orders/123 |
| PATCH | 部分更新 | なし | なし | /api/orders/123 |
| DELETE | 削除 | あり | なし | /api/orders/123 |
4. @RestControllerとリクエストマッピング
(1) @RestControllerの仕組み
@RestController = @Controller + @ResponseBody。このクラスのすべてのメソッドの戻り値がHTTPレスポンスボディに直接書き込まれる(デフォルトでJSON)ことを示します。
(1) ▶ サンプル:基本的なOrderController
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final List<Map<String, Object>> orders = new ArrayList<>();
@GetMapping
public List<Map<String, Object>> listOrders() {
return orders;
}
@PostMapping
public Map<String, Object> createOrder(
@RequestBody Map<String, Object> order) {
order.put("id", (long) (orders.size() + 1));
order.put("status", "PENDING");
orders.add(order);
return order;
}
}
出力:
// 実行成功
(2) リクエストパラメータのバインディング
(2) ▶ サンプル:@PathVariableと@RequestParam
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable Long id) {
return Map.of("id", id, "status", "PENDING");
}
@GetMapping
public List<Map<String, Object>> searchOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status) {
return List.of(Map.of(
"page", page, "size", size, "status", status
));
}
}
出力:
// 実行成功
| アノテーション | 用途 | 例 | URL |
|---|---|---|---|
@PathVariable |
パスから変数を抽出 | @PathVariable Long id |
/api/orders/123 |
@RequestParam |
クエリパラメータを抽出 | @RequestParam String status |
/api/orders?status=PENDING |
@RequestBody |
リクエストボディからJSONを抽出 | @RequestBody OrderRequest req |
POSTボディ |
@RequestHeader |
リクエストヘッダーを抽出 | @RequestHeader String auth |
Header: Authorization |
(3) ▶ サンプル:DTOを使ったリクエストボディの受信
public record CreateOrderRequest(
Long productId,
Integer quantity,
String shippingAddress
) {}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<Map<String, Object>> createOrder(
@RequestBody CreateOrderRequest request) {
Map<String, Object> order = Map.of(
"productId", request.productId(),
"quantity", request.quantity(),
"shippingAddress", request.shippingAddress(),
"status", "PENDING"
);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(order);
}
}
出力:
// 実行成功
5. ResponseEntityとHTTPステータスコード
(1) ResponseEntityの使用
ResponseEntityによりHTTPレスポンスを完全に制御できます。ステータスコード、レスポンスヘッダー、レスポンスボディを設定できます。
| メソッド | 適用シナリオ | 柔軟性 |
|---|---|---|
| オブジェクトを直接返す | シンプルな成功レスポンス | 低 (200で固定) |
ResponseEntity.ok(body) |
200を設定する場合 | 中 |
ResponseEntity.status(CREATED).body(body) |
リソース作成で201を返す場合 | 高 |
ResponseEntity.notFound().build() |
リソース不明で404を返す場合 | 高 |
(1) ▶ サンプル:完全なCRUD操作
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> orderStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest request) {
Long id = idGenerator.getAndIncrement();
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "PENDING");
orderStore.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOne(@PathVariable Long id) {
Map<String, Object> order = orderStore.get(id);
if (order == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(order);
}
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> update(
@PathVariable Long id,
@RequestBody CreateOrderRequest request) {
if (!orderStore.containsKey(id)) {
return ResponseEntity.notFound().build();
}
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "CONFIRMED");
orderStore.put(id, order);
return ResponseEntity.ok(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (orderStore.remove(id) == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build();
}
}
出力:
// 実行成功
6. APIテスト方法
(1) curlテストコマンド
(1) ▶ サンプル:curlでCRUD APIをテスト
# 注文の作成
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"productId":1, "quantity":3, "shippingAddress":"123 Main St"}'
# 注文の取得
curl http://localhost:8080/api/orders/1
# ページネーション付き注文一覧
curl "http://localhost:8080/api/orders?page=0&size=10"
# 注文の更新
curl -X PUT http://localhost:8080/api/orders/1 \
-H "Content-Type: application/json" \
-d '{"productId":2, "quantity":5, "shippingAddress":"456 Oak Ave"}'
# 注文の削除
curl -X DELETE http://localhost:8080/api/orders/1
出力:
{"status":"ok","data":{}}
| HTTPステータスコード | 意味 | 返されるタイミング |
|---|---|---|
| 200 OK | 成功 | GET / PUT成功 |
| 201 Created | 作成 | POSTリソース作成成功 |
| 204 No Content | 内容なし | DELETE成功 |
| 400 Bad Request | リクエストエラー | パラメータ検証失敗 |
| 404 Not Found | 見つからない | リソースが存在しない |
7. RESTful API設計仕様
(1) URL命名規則
| ルール | 誤った形式 | 正しい形式 |
|---|---|---|
| 複数名詞の使用 | /getOrder |
/api/orders |
| ネストされたリソース関係 | /orderItems?orderId=1 |
/api/orders/1/items |
| クエリよりパスを使用 | /api/orders?id=1 |
/api/orders/1 |
| バージョニング | なし | /api/v1/orders |
| フィルタリングにはクエリパラメータ | /api/pendingOrders |
/api/orders?status=PENDING |
8. 総合サンプル:OrderFlow注文CRUD API
package com.orderflow.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public record CreateOrderRequest(
Long productId, Integer quantity, String shippingAddress
) {}
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest req) {
Long id = seq.getAndIncrement();
Map<String, Object> order = new LinkedHashMap<>();
order.put("id", id);
order.put("productId", req.productId());
order.put("quantity", req.quantity());
order.put("shippingAddress", req.shippingAddress());
order.put("status", "PENDING");
order.put("createdAt", java.time.Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping
public List<Map<String, Object>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return store.values().stream()
.skip((long) page * size).limit(size).toList();
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> get(@PathVariable Long id) {
Map<String, Object> order = store.get(id);
return order != null ? ResponseEntity.ok(order)
: ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
❓ よくある質問
ResponseEntityを返すべきか, オブジェクトを直接返すべきですか?ResponseEntityを使います。application.ymlで設定するか, フィールドに@JsonFormat(pattern = "yyyy-MM-dd")を使用できます。/api/v1/orders)を推奨します。シンプルで直感的です。ヘッダーベースのバージョニング (Accept: application/vnd.orderflow.v1+json)も可能で, よりRESTfulですが実装が複雑になります。📖 まとめ
- RESTはCRUD操作をHTTP動詞にマッピング:GETは照会, POSTは作成, PUTは更新, DELETEは削除
@RestController=@Controller+@ResponseBody, JSONを返す@PathVariableはパス変数を取得,@RequestParamはクエリパラメータを取得,@RequestBodyはリクエストボディを取得ResponseEntityでレスポンスのステータスコード, ヘッダー, ボディを完全に制御- RESTful URLは複数名詞を使用。ネストはリソース間の関係を示し, クエリパラメータはフィルタリングに使用
📝 練習問題
-
基本問題 (難易度 ⭐):OrderFlowのProductリソースのCRUD APIを実装してください。
GET /api/v1/products,POST /api/v1/products,GET /api/v1/products/{id},DELETE /api/v1/products/{id}を含みます。 -
応用問題 (難易度 ⭐⭐):Order APIに
PATCH /api/v1/orders/{id}/statusエンドポイントを追加し, 注文ステータスフィールドのみを更新してください。PATCHとPUTの違いについて考察してください。 -
チャレンジ問題 (難易度 ⭐⭐⭐):
GET /api/v1/orders/{id}/itemsネストされたリソースエンドポイントを実装し, 指定した注文のすべての注文項目を返してください。ページネーションパラメータもサポートすること。



