はじめに
REST API は、最新のバックエンド アプリケーションにおける最も一般的な通信方法です。 Spring Boot は、RESTful API を構築するための強力で成熟したフレームワークである Spring Web MVC を提供します。この記事では、コントローラーを操作する際の基本的なテクニックから高度なテクニックまでガイドします。
1. HTTP メソッドと REST の規則
1.1 RESTful リソース マッピング
Resource: /api/users
GET /api/users → Lấy danh sách users
GET /api/users/{id} → Lấy user theo ID
POST /api/users → Tạo user mới
PUT /api/users/{id} → Cập nhật toàn bộ user
PATCH /api/users/{id} → Cập nhật một phần user
DELETE /api/users/{id} → Xóa user
1.2 重要な HTTP ステータス コード
| コード | 意味 | いつ使用するか |
|---|---|---|
| 200 | OK | GET、PUT、PATCH が成功しました |
| 201 | 作成された | POST はリソースを正常に作成しました |
| 204 | コンテンツなし | 削除が成功しました |
| 400 | 不正なリクエスト | 無効な入力 |
| 401 | 不正 | 未検証 |
| 403 | 禁止 | 権限がありません |
| 404 | 見つかりません | リソースが存在しません |
| 409 | 紛争 | データの重複 (メールの重複) |
| 500 | 内部サーバーエラー | サーバーエラー |
2. @RestController とリクエストのマッピング
2.1 基本コントローラー
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
// GET /api/v1/products
@GetMapping
public List<Product> getAllProducts() {
return productService.findAll();
}
// GET /api/v1/products/123
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// POST /api/v1/products
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Product createProduct(@RequestBody Product product) {
return productService.create(product);
}
// PUT /api/v1/products/123
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id,
@RequestBody Product product) {
return productService.update(id, product);
}
// DELETE /api/v1/products/123
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteProduct(@PathVariable Long id) {
productService.delete(id);
}
}
2.2 @RestController と @Controller
// @RestController = @Controller + @ResponseBody
// Mọi method tự động serialize return value thành JSON
@RestController // Dùng cho REST API
public class ApiController {
@GetMapping("/api/data")
public Map<String, String> getData() {
return Map.of("key", "value"); // → JSON response
}
}
@Controller // Dùng cho server-side rendering (Thymeleaf)
public class WebController {
@GetMapping("/home")
public String home(Model model) {
model.addAttribute("title", "Home");
return "home"; // → Render template home.html
}
}
3. リクエストからデータを受信する
3.1 パス変数
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable("orderId") Long orderId) { // Custom name mapping
return orderService.findByUserAndId(userId, orderId);
}
// Optional path variable
@GetMapping({"/files", "/files/{filename}"})
public String getFile(
@PathVariable(required = false) String filename) {
return filename != null ? filename : "index.html";
}
3.2 クエリパラメータ
// GET /api/products?category=electronics&minPrice=100&maxPrice=500
@GetMapping
public List<Product> searchProducts(
@RequestParam String category,
@RequestParam(defaultValue = "0") double minPrice,
@RequestParam(defaultValue = "999999") double maxPrice,
@RequestParam(required = false) String brand) {
return productService.search(category, minPrice, maxPrice, brand);
}
// Nhận tất cả params dưới dạng Map
@GetMapping("/search")
public List<Product> search(@RequestParam Map<String, String> params) {
return productService.searchByParams(params);
}
3.3 リクエストヘッダー
@GetMapping("/api/profile")
public UserProfile getProfile(
@RequestHeader("Authorization") String authHeader,
@RequestHeader(value = "Accept-Language", defaultValue = "vi") String lang) {
String token = authHeader.replace("Bearer ", "");
return userService.getProfile(token, lang);
}
3.4 リクエスト本文
// POST /api/users
// Content-Type: application/json
// Body: {"name": "Duy", "email": "[email protected]"}
@PostMapping
public User createUser(@RequestBody CreateUserRequest request) {
return userService.create(request);
}
// Record class cho request
public record CreateUserRequest(
String name,
String email,
String password
) {}
4. ResponseEntity — 応答コントロール
4.1 基本
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product created = productService.create(product);
URI location = URI.create("/api/v1/products/" + created.getId());
return ResponseEntity
.created(location) // 201 Created + Location header
.body(created);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build(); // 204 No Content
}
4.2 カスタムヘッダー
@GetMapping("/download/{fileId}")
public ResponseEntity<byte[]> downloadFile(@PathVariable String fileId) {
FileData file = fileService.getFile(fileId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.name() + "\"")
.header(HttpHeaders.CONTENT_TYPE, file.contentType())
.header("X-File-Size", String.valueOf(file.size()))
.body(file.data());
}
5. コンテンツの交渉
5.1 生産と消費
@RestController
@RequestMapping("/api/v1/reports")
public class ReportController {
// Chỉ chấp nhận JSON input, trả về JSON
@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public Report createJsonReport(@RequestBody ReportRequest request) {
return reportService.generate(request);
}
// Trả về CSV
@GetMapping(value = "/{id}/csv", produces = "text/csv")
public String exportCsv(@PathVariable Long id) {
return reportService.exportCsv(id);
}
}
6. API のバージョン管理戦略
6.1 URL パスのバージョン管理 (推奨)
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
@GetMapping("/{id}")
public UserV1Response getUser(@PathVariable Long id) { }
}
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
@GetMapping("/{id}")
public UserV2Response getUser(@PathVariable Long id) { }
}
6.2 ヘッダーのバージョン管理
@GetMapping(value = "/users/{id}", headers = "X-API-Version=1")
public UserV1Response getUserV1(@PathVariable Long id) { }
@GetMapping(value = "/users/{id}", headers = "X-API-Version=2")
public UserV2Response getUserV2(@PathVariable Long id) { }
7. 実際の例: 完全な CRUD API
@RestController
@RequestMapping("/api/v1/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public ResponseEntity<List<TaskResponse>> getAllTasks(
@RequestParam(defaultValue = "all") String status) {
List<TaskResponse> tasks = taskService.findByStatus(status);
return ResponseEntity.ok(tasks);
}
@GetMapping("/{id}")
public ResponseEntity<TaskResponse> getTask(@PathVariable Long id) {
return taskService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<TaskResponse> createTask(
@RequestBody CreateTaskRequest request) {
TaskResponse task = taskService.create(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(task.id())
.toUri();
return ResponseEntity.created(location).body(task);
}
@PatchMapping("/{id}")
public ResponseEntity<TaskResponse> updateTask(
@PathVariable Long id,
@RequestBody UpdateTaskRequest request) {
return taskService.update(id, request)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
taskService.delete(id);
return ResponseEntity.noContent().build();
}
}
// Request/Response records
public record CreateTaskRequest(String title, String description) {}
public record UpdateTaskRequest(String title, String description, String status) {}
public record TaskResponse(Long id, String title, String description,
String status, LocalDateTime createdAt) {}
概要
- @RestController は @Controller + @ResponseBody を結合し、応答を自動的に JSON にシリアル化します。
- CRUD 操作には @GetMapping、@PostMapping、@PutMapping、@PatchMapping、@DeleteMapping を使用します。
- ResponseEntity により、HTTP ステータス コード、ヘッダー、本文を完全に制御できます
- API のバージョン管理では、簡潔さと明確さのために URL パス (/api/v1/) を使用する必要があります。
演習
- エンティティの CRUD REST API を作成する
Book完全な HTTP メソッドを使用した (ID、タイトル、著者、isbn、価格、発行年) - 検索エンドポイントを実装します。
GET /api/books?author=X&minPrice=Y&maxPrice=Zすべてのオプションのパラメータを使用して - ResponseEntity を使用して正しいステータス コードを返します (201 Created with Location ヘッダー、204 No Content for DELETE、404 for resource doesn't know)