99 Ngày Spring — Ngày 15: ResponseEntity & status code
API của ta nhận request đã chuẩn (Ngày 12–14), nhưng trả lời thì vẫn quê: tạo mới xong trả 200 trống trơn, tìm không thấy cũng… 200. Trong HTTP, status code là câu đầu tiên của mọi câu trả lời — máy móc (cache, monitoring, client SDK) đọc nó trước cả body. Hôm nay ta học nói cho đúng: bảng status tối thiểu, ResponseEntity để điều khiển trọn vẹn response, và nghi thức 201 Created + Location của dân REST chuyên nghiệp.

Status code — ngữ pháp của HTTP
// Bảng status code tối thiểu cho REST API tử tế:
//
// 2xx — THÀNH CÔNG
// 200 OK đọc/sửa thành công (GET, PUT)
// 201 Created TẠO MỚI thành công (POST) — kèm header Location!
// 204 No Content thành công, không có gì để trả (DELETE)
//
// 4xx — LỖI PHÍA CLIENT
// 400 Bad Request body/tham số sai (validation Ngày 14 trả cái này)
// 404 Not Found tài nguyên không tồn tại
// 409 Conflict đụng độ trạng thái (slug trùng, đặt chỗ đã kín)
//
// 5xx — LỖI PHÍA SERVER (bug, DB sập — KHÔNG phải lỗi của client)
// 500 Internal Server Error
//
// Tội kinh điển: cái gì cũng 200 — kể cả lỗi, nhét {"success": false} vào body.
// Client phải MỔ BODY mới biết hỏng; cache, monitoring, retry đều mù. ĐỪNG.Ranh giới quan trọng nhất nằm giữa 4xx và 5xx: 4xx nghĩa là "bạn gửi sai — sửa rồi hãy gửi lại", 5xx nghĩa là "tôi hỏng — thử lại sau". Trộn lẫn hai loại là phá hỏng monitoring lẫn chiến lược retry của client.
Nhớ bảng nhỏ này là đủ 90% cuộc đời: 200 / 201 / 204 / 400 / 404 / 409 / 500 — đừng vội học thuộc cả trăm mã.
ResponseEntity — cầm lái toàn bộ response
@RestController
@RequestMapping("/api/posts")
public class PostController {
// Trả object trần (Ngày 11) = 200 mặc định — vẫn hợp lệ khi chỉ cần thế!
@GetMapping
public List<PostResponse> list() { ... }
// ResponseEntity = quyền điều khiển ĐẦY ĐỦ: status + header + body
@GetMapping("/{id}")
public ResponseEntity<PostResponse> detail(@PathVariable Long id) {
return postService.find(id) // Optional<Post>
.map(PostResponse::from)
.map(ResponseEntity::ok) // có → 200 + body
.orElse(ResponseEntity.notFound().build()); // không → 404
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
postService.delete(id);
return ResponseEntity.noContent().build(); // 204 — xóa là hết chuyện
}
}ResponseEntity<T>gói ba thứ: status + header + body — các factory method (ok,notFound,noContent) đọc lên thành văn.Mẫu
Optional → map → orElse(404)là chuẩn mực cho "tìm theo id": có thì 200, không thì 404 — không bao giờ trả 200 với bodynull. (Optionalmổ xẻ kỹ ở series Java Ngày 58.)DELETEthành công trả 204 No Content — "xong việc, không có gì để kể"; trả lại object vừa xóa là thói quen lạ cần bỏ.
201 Created — nghi thức tạo mới chuẩn REST
@PostMapping
public ResponseEntity<PostResponse> create(
@Valid @RequestBody CreatePostRequest req) { // Ngày 13 + 14 đứng gác
Post post = postService.create(req);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest() // /api/posts
.path("/{id}") // + /42
.buildAndExpand(post.getId())
.toUri();
return ResponseEntity
.created(location) // 201 + Location: /api/posts/42
.body(PostResponse.from(post));
}
// Nghi thức 201 chuẩn REST: status nói "đã tạo",
// header Location nói "nó ở ĐÂY" — client lấy id mà không cần mổ body.
// (Cùng ngày bên series Java: đa hình — PostResponse.from cũng là
// "một hợp đồng, nhiều hình hài" đấy nhé!)Ba mảnh của một lần tạo mới tử tế: status
201(đã tạo) + headerLocation(địa chỉ tài nguyên mới) + body (bản ghi vừa sinh kèmid) — client muốn dùng kiểu gì cũng chiều được.ServletUriComponentsBuilder.fromCurrentRequest()dựngLocationtừ chính URL đang gọi — không hard-code domain, đổi môi trường (Ngày 09!) không vỡ.Toàn bộ dàn phòng thủ tuần này đứng chung một khung hình:
@Valid(Ngày 14) gác cửa vào, DTO (Ngày 13) làm hợp đồng, hôm nayResponseEntitychỉnh câu trả lời.
Header, cache & nguyên tắc nhất quán
// Header tùy chỉnh khi cần:
return ResponseEntity.ok()
.header("X-Total-Count", String.valueOf(total)) // ví dụ: tổng bản ghi
.cacheControl(CacheControl.maxAge(Duration.ofMinutes(5))) // Cache-Control
.body(items);
// Nguyên tắc chọn kiểu trả về cho controller — giữ NHẤT QUÁN:
// 1. Chỉ cần 200 + body? → trả object trần cho gọn
// 2. Status/header thay đổi theo logic? → ResponseEntity
// 3. Lỗi (404/409/500)? → NÉM EXCEPTION, đừng tự build response lỗi
// rải rác — Ngày 16: @ControllerAdvice gom xử lý lỗi về MỘT chỗQuy tắc ba nhánh trong code trên đáng dán lên màn hình — đặc biệt nhánh 3: đừng tự build response lỗi trong từng controller; ném exception và để một nơi duy nhất dịch thành HTTP — chính là bài ngày mai.
Đội ngũ ăn ý là đội ngũ thống nhất một kiểu: API mà chỗ trả
ResponseEntity, chỗ trả object trần, chỗ 200-cho-tất là API khó tin cậy.
Bài tập nhỏ
Nâng cấp
GET /api/books/{id}theo mẫuOptional → 404— thử id có thật và id ma, soi status bằngcurl -i.Nâng cấp
POST /api/bookstrả 201 + Location — lấy URL từ header rồi GET theo nó để khép vòng.Thêm
DELETE /api/books/{id}trả 204 — xóa hai lần liên tiếp: lần hai nên trả gì? (Tranh luận 204 vs 404 — chọn và bảo vệ lập luận của bạn.)Gắn
X-Total-Countvào endpoint danh sách — đọc header đó từcurl -i.
Kết luận
API giờ nói chuẩn ngữ pháp HTTP: bảng status tối thiểu với ranh giới 4xx/5xx rạch ròi, ResponseEntity cầm lái status–header–body, nghi thức 201 + Location cho tạo mới và 204 cho xóa, cùng nguyên tắc vàng "lỗi thì ném, đừng tự build". Nguyên tắc ấy dẫn thẳng tới Ngày 16: xử lý lỗi toàn cục — @ControllerAdvice, @ExceptionHandler và chuẩn ProblemDetail (RFC 7807) gom mọi tình huống lỗi về một mối, đẹp và nhất quán. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


