CRUD実装例

@RestControllerの真価は、Webアプリケーションのフロントエンド（React, Vue, Angularなど）や、スマートフォンアプリ、他のマイクロサービスとデータをやり取りするためのREST APIを構築する際に発揮されるものである。
ここでは、典型的な「Todo（やることリスト）」管理APIを例に、データの一覧取得、新規作成、更新、削除 (CRUD) を一通り実装する。
事前準備：扱うデータ (Model)
まず、APIでやり取りするデータとなる Todo クラスと、リクエストボディを受け取るための TodoRequest クラスを用意する必要がある。
// Todo.java - データの実体 (JPAエンティティなどを想定)
public class Todo {
    private Long id;
    private String title;
    private boolean completed;
    // コンストラクタ、Getters/Settersは省略
}

// TodoRequest.java - POSTやPUTでJSONデータを受け取るためのクラス
public class TodoRequest {
    private String title;
    private Boolean completed;
    // Getters/Settersは省略
}

@RestController によるCRUD実装
TodoApiController.java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@RestController
@RequestMapping("/api/todos") // このコントローラー内のメソッドは全て /api/todos から始まる
public class TodoApiController {

    // --- 本来はサービスクラスやリポジトリを利用するが、ここでは簡易的なインメモリDBで代用する ---
    private final Map<Long, Todo> todoDatabase = new ConcurrentHashMap<>();
    private final AtomicLong idCounter = new AtomicLong();

    public TodoApiController() {
        // 初期データ
        long id1 = idCounter.incrementAndGet();
        todoDatabase.put(id1, new Todo(id1, "Spring Bootの学習", false));
        long id2 = idCounter.incrementAndGet();
        todoDatabase.put(id2, new Todo(id2, "REST APIの実装", false));
    }
    // ---------------------------------------------------------------------------------


    /**
     * 1.【一覧取得】GET /api/todos - 複数のリソースを取得する
     * @return 全てのTodoのリスト
     */
    @GetMapping
    public List<Todo> getAllTodos() {
        return List.copyOf(todoDatabase.values());
    }


    /**
     * 2.【新規作成】POST /api/todos - 新しいリソースを作成する
     * @param request リクエストボディのJSONデータがマッピングされる
     * @return 作成されたTodoオブジェクトと、HTTPステータス 201 Created
     */
    @PostMapping
    public ResponseEntity<Todo> createTodo(@RequestBody TodoRequest request) {
        long newId = idCounter.incrementAndGet();
        Todo newTodo = new Todo(newId, request.getTitle(), false);
        todoDatabase.put(newId, newTodo);
        
        // ResponseEntityの使用により、ステータスコードやヘッダーの詳細な制御が可能となる
        return new ResponseEntity<>(newTodo, HttpStatus.CREATED); // 201 Createdを返却
    }


    /**
     * 3.【更新】PUT /api/todos/{id} - 既存のリソースを更新する
     * @param id URLのパスから取得するID
     * @param request 更新内容のJSONデータ
     * @return 更新後のTodoオブジェクト or 404 Not Found
     */
    @PutMapping("/{id}")
    public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody TodoRequest request) {
        if (!todoDatabase.containsKey(id)) {
            // 対象が見つからない場合は 404 Not Found を返却
            return ResponseEntity.notFound().build();
        }
        
        Todo updatedTodo = todoDatabase.get(id);
        updatedTodo.setTitle(request.getTitle());
        updatedTodo.setCompleted(request.getCompleted());
        
        return ResponseEntity.ok(updatedTodo); // 200 OK と更新後のデータを返却
    }


    /**
     * 4.【削除】DELETE /api/todos/{id} - リソースを削除する
     * @param id 削除対象のID
     * @return 成功時は 204 No Content
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
        if (!todoDatabase.containsKey(id)) {
            return ResponseEntity.notFound().build();
        }
        
        todoDatabase.remove(id);
        
        // 成功したが返却するコンテンツが無い場合は 204 No Content を返却
        return ResponseEntity.noContent().build();
    }
}

JavaScript (fetch) からの利用例
上記のAPIをクライアントサイドのJavaScriptから利用する例は以下の通りである。
1.【一覧取得】
fetch('/api/todos')
    .then(response => response.json())
    .then(todos => console.log('取得したTodoリスト:', todos));

2.【新規作成】
const newTodoData = { title: '新しいタスク' };

fetch('/api/todos', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json' // JSON形式のデータを送信することを明記
    },
    body: JSON.stringify(newTodoData) // JavaScriptオブジェクトをJSON文字列に変換
})
.then(response => {
    console.log('ステータスコード:', response.status); // 201
    return response.json();
})
.then(createdTodo => console.log('作成されたTodo:', createdTodo));

要点: @RequestBody でデータを受け取るためには、Content-Type: application/json ヘッダーと、JSON.stringify() で文字列化したリクエストボディが必要である。
3.【更新】
const updatedTodoData = { title: '更新されたタスク', completed: true };
const targetId = 1;

fetch(`/api/todos/${targetId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(updatedTodoData)
})
.then(response => response.json())
.then(updatedTodo => console.log('更新後のTodo:', updatedTodo));

要点: @PathVariable と @RequestBody の組み合わせにより、どのリソースを、どの内容で更新するかを指定する。
4.【削除】
const deleteId = 2;

fetch(`/api/todos/${deleteId}`, {
    method: 'DELETE'
})
.then(response => {
    console.log('ステータスコード:', response.status); // 204
    if (response.ok) {
        console.log(`ID:${deleteId}のTodoが削除された。`);
    }
});

要点: DELETE メソッドを用い、パスで削除対象を指定する。成功時のレスポンスボディは空であるため（204 No Content）、.json() でのパースは不要である。
まとめ：重要なアノテーション
| HTTPメソッド | URL例 | コントローラーのアノテーション | 主な役割 | 重要な関連アノテーション |
|---|---|---|---|---|
| GET | /api/todos | @GetMapping | リソースの一覧や詳細の取得 | @PathVariable, @RequestParam |
| POST | /api/todos | @PostMapping | 新規リソースの作成 | @RequestBody |
| PUT | /api/todos/{id} | @PutMapping | 既存リソースの全体的な更新 | @PathVariable, @RequestBody |
| DELETE | /api/todos/{id} | @DeleteMapping | 既存リソースの削除 | @PathVariable |
これらのパターンを組み合わせることで、REST APIの基本的な要件を満たすことが可能である。