Spring Boot bài 20: REST Controller, JSON và DTO cho API danh sách
- 18-10-2026
- Toanngo92
- 0 Comments
Giáo trình Spring Boot MVC và REST API · Chương 9/12: REST API và hợp đồng HTTP · Bài 20/28 · Biên soạn tháng 10/2026.
Mục lục
Mục tiêu bài học
Tạo endpoint đọc dữ liệu bằng JSON, dùng DTO và envelope phân trang ổn định thay vì trả entity trực tiếp.
Cùng nghiệp vụ, khác cách trả kết quả
CourseWebController trả tên template; CourseApiController dùng @RestController để kết quả được ghi vào response body qua message converter. Hai Controller gọi cùng CourseService, nên không có hai phiên bản quy tắc cập nhật khóa học. @RequestMapping("/api") tạo tiền tố chung cho nhóm endpoint.
GET /api/courses trả danh sách có phân trang. GET /api/courses/{id} trả một khóa học. Đây là các thao tác đọc công khai theo bảng quyền của mẫu. HTTP method mang ý nghĩa: GET cần không tạo thay đổi nghiệp vụ như thêm dòng hoặc gửi email.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/CourseApiController.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
@RestController @RequestMapping("/api")
public class CourseApiController {
private final CourseService service;
public CourseApiController(CourseService service){this.service=service;}
@GetMapping("/courses") PageDto<CourseDto> list(@RequestParam(defaultValue="") String q,@RequestParam(defaultValue="0") int page,@RequestParam(defaultValue="10") int size,@RequestParam(defaultValue="id") String sort){return PageDto.from(service.search(q,page,size,sort));}
@GetMapping("/courses/{id}") CourseDto get(@PathVariable Long id){return service.get(id);}
DTO là hợp đồng dữ liệu
CourseDto gồm id, title, price, categoryId và categoryName. JSON không chứa đối tượng Category với toàn bộ thuộc tính hoặc proxy Hibernate. Việc tách entity và DTO giúp thay đổi schema nội bộ mà không nhất thiết thay hợp đồng công khai.
Request tạo/sửa dùng CourseForm có validation. Response dùng CourseDto có id do server cấp. Dù tên CourseForm gợi giao diện HTML, nó hiện là đối tượng đầu vào dùng chung có phạm vi nhỏ; khi web và API có quy tắc khác nhau, hãy tách request DTO thay vì ép hai luồng dùng một cấu trúc không còn phù hợp.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/PageDto.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
package com.hocvietcode.academy;
import java.util.List;
import org.springframework.data.domain.Page;
public record PageDto<T>(List<T> content,int page,int size,long totalElements,int totalPages) {
public static <T> PageDto<T> from(Page<T> p){return new PageDto<>(p.getContent(),p.getNumber(),p.getSize(),p.getTotalElements(),p.getTotalPages());}
}
Cấu trúc phân trang dễ dùng cho client
PageDto chủ động trả năm trường: content, page, size, totalElements, totalPages. Client không cần biết PageImpl của Spring Data có thêm những thuộc tính nội bộ nào. page vẫn bắt đầu từ 0 như trong endpoint HTML, còn giao diện người dùng có thể hiển thị số trang từ 1.
Thử GET /api/courses?size=1&q=Spring&sort=title bằng trình duyệt hoặc công cụ HTTP. Kết quả phải có mảng content và thông tin tổng. Khi không có bản ghi phù hợp, một danh sách rỗng với 200 là hợp lý; điều đó khác với GET một id không tồn tại trả 404.
curl -i "http://localhost:8080/api/courses?size=1&q=Spring&sort=title"
curl -i "http://localhost:8080/api/courses/1"
Content negotiation và cách đọc lỗi
Client nên gửi Accept: application/json khi muốn JSON. POST JSON cần Content-Type: application/json để server biết cách đọc body; Accept và Content-Type không cùng vai trò. Mã status cho biết kết quả tổng quát, còn body bổ sung dữ liệu hoặc chi tiết lỗi.
Không bọc mọi kết quả trong HTTP 200 rồi nhét success=false vào JSON, vì client, cache và công cụ giám sát sẽ mất tín hiệu chuẩn. Ở bài tiếp theo, 201, 204, 400 và 404 được dùng theo từng thao tác. API cũng không tự trở nên bảo mật chỉ vì URL có tiền tố /api.
Lỗi thường gặp và lưu ý
- Không trả JpaRepository hoặc entity lazy ra response và để serializer quyết định hợp đồng.
- GET danh sách rỗng là trường hợp bình thường, không cần 404.
Ghi nhớ
REST Controller chuyển kết quả nghiệp vụ thành HTTP/JSON; DTO giữ cấu trúc response rõ ràng và có thể kiểm thử.

