Backend

99 Ngày Spring — Ngày 11: REST controller đầu tiên

SSite Admin
7 tháng 08, 2026 5 phút đọc 14 lượt xem
99 Ngày Spring — Ngày 11: REST controller đầu tiên

Mười ngày đầu ta mài giũa "nội công": container, bean, DI, cấu hình. Hôm nay bắt đầu Giai đoạn 2 — xây REST API: làm thứ mà trình duyệt, app di động hay service khác thật sự gọi được. Nhân vật chính là @RestController — và bạn sẽ thấy chỉ cần vài dòng: không XML, không servlet tay, không cấu hình server — mọi nền móng Ngày 02–10 giờ gặt quả.

Sketchnote Ngày 11: REST controller đầu tiên — @RestController, @GetMapping, JSON tự động và hành trình request

REST 30 giây: tài nguyên & động từ

  • REST nói gọn: mọi thứ là tài nguyên định danh bằng URL (/api/posts, /api/posts/1), thao tác bằng động từ HTTP: GET đọc, POST tạo, PUT sửa, DELETE xóa.

  • Dữ liệu trao đổi mặc định là JSON — nhẹ, mọi ngôn ngữ đọc được; server và client nhờ đó tách rời hoàn toàn (web, mobile, service khác đều xài chung một API).

  • Hôm nay ta tập trung GET — các động từ còn lại lần lượt xuất hiện trong tuần này.

Controller đầu tiên: 5 dòng là chạy

@RestController                          // = @Controller + @ResponseBodypublic class HelloController {

    @GetMapping("/hello")                // GET /hello → chạy method này
    public String hello() {
        return "Xin chào từ Spring Boot!";   // chuỗi trả thẳng vào response body
    }
}

// Chạy app (Ngày 02) rồi mở trình duyệt / curl:// GET http://localhost:8080/hello// → 200 OK, body: Xin chào từ Spring Boot!
  • @RestController là stereotype (họ hàng @Component — Ngày 05): component scan nhặt nó thành bean, kiêm lời hứa "giá trị method trả về đi thẳng vào response body".

  • @GetMapping("/hello") khắc biển chỉ đường: GET tới /hello thì gọi method này. Nó là dạng gọn của @RequestMapping(method = GET).

  • Không cần main nào khác, không đăng ký gì thêm — chạy app là endpoint sống. So với thời Java EE phải viết servlet + web.xml, đây chính là lý do Spring Boot thắng cuộc (Ngày 01).

Trả JSON: Jackson lo phần nặng nhọc

Chuỗi thô mới là khởi động — API thật trả dữ liệu có cấu trúc. Điều tuyệt nhất: bạn cứ trả object Java, phần còn lại đã có người lo:

public record Post(Long id, String title, boolean published) { }

@RestControllerpublic class PostController {

    @GetMapping("/api/posts")
    public List<Post> list() {           // trả OBJECT — không phải String!
        return List.of(
            new Post(1L, "Ngày 11: REST controller", true),
            new Post(2L, "Bản nháp ngày mai", false)
        );
    }
}

// GET /api/posts → Jackson TỰ serialize thành JSON:// [//   { "id": 1, "title": "Ngày 11: REST controller", "published": true },//   { "id": 2, "title": "Bản nháp ngày mai", "published": false }// ]// Content-Type: application/json — không viết một dòng chuyển đổi nào!
  • Jackson — thư viện JSON đi kèm starter web — tự serialize mọi object/record/list thành JSON, đặt luôn Content-Type: application/json.

  • Dùng record (Java 21) làm kiểu trả về rất hợp: gọn, bất biến, tên field thành tên key JSON. Java thuần sẽ mổ xẻ record kỹ ở Ngày 20 của series Java.

  • Vì sao Jackson "tự nhiên" có mặt? Auto-configuration Ngày 10: classpath có Jackson → @ConditionalOnClass đậu → HttpMessageConverter JSON được dựng sẵn.

Hành trình một request

// Hành trình một request GET /api/posts://// Trình duyệt/khách hàng//   → Tomcat (server nhúng — starter web mang theo, Ngày 02)//     → DispatcherServlet (bưu điện trung tâm của Spring MVC)//       → HandlerMapping: "/api/posts" thuộc về PostController.list()//         → gọi method — controller là BEAN (Ngày 03!), được DI đầy đủ//       → giá trị trả về → Jackson (HttpMessageConverter) → JSON//   ← 200 OK + application/json//// DispatcherServlet & bạn bè đều do AUTO-CONFIGURATION dựng (Ngày 10!)// — thấy spring-boot-starter-web trên classpath là cả dây chuyền mọc ra

Nắm sơ đồ này là nắm xương sống Spring MVC: DispatcherServlet nhận mọi request rồi phân phát cho controller khớp đường dẫn — vì thế gọi là mô hình front controller. Từng mắt xích (mapping, converter, xử lý lỗi) đều là bean thay được — nhưng mặc định của Boot đã đủ tốt để ta chỉ viết controller.

Controller mỏng, service dày

Thói quen vàng nên tập từ ngày đầu tiên: controller chỉ nhận request và trả response — nghiệp vụ đẩy xuống tầng @Service (Ngày 05), nối nhau bằng constructor injection (Ngày 04):

@RestControllerpublic class PostController {
    private final PostService postService;      // controller MỎNG: nhận & trả

    PostController(PostService postService) {   // constructor injection — Ngày 04!
        this.postService = postService;
    }

    @GetMapping("/api/posts")
    public List<Post> list() {
        return postService.findAll();           // nghiệp vụ ở tầng service
    }
}

@Servicepublic class PostService {
    public List<Post> findAll() {               // mai mốt: gọi repository, cache…
        return List.of(new Post(1L, "Bài đầu tiên", true));
    }
}
  • Controller mỏng dễ đọc, dễ test; service không dính gì tới HTTP nên tái sử dụng được ở scheduler, listener, CLI…

  • Chuỗi phân tầng đầy đủ controller → service → repository sẽ khép kín khi gặp Spring Data JPA (Ngày 21+).

Bài tập nhỏ

  • Tạo HelloController với GET /hello trả tên bạn — chạy và gọi bằng trình duyệt lẫn curl.

  • Tạo record Book(Long id, String title, double price) và endpoint GET /api/books trả 3 cuốn — ngắm JSON tự sinh.

  • Tách BookService rồi tiêm vào controller bằng constructor — xác nhận kết quả không đổi.

  • Gọi một đường dẫn không tồn tại (GET /nope) — xem JSON lỗi 404 mặc định của Boot; Ngày 16 ta sẽ tự may trang lỗi đẹp hơn.

Kết luận

Ứng dụng của bạn đã nói chuyện được với thế giới: @RestController + @GetMapping mở endpoint trong vài dòng, Jackson tự đổi object thành JSON, DispatcherServlet điều phối sau cánh gà, và kiến trúc controller-mỏng-service-dày đặt nền cho mọi ngày sau. Nhưng API mới chỉ biết trả một danh sách cứngNgày 12 ta học nhận tham số: @PathVariable cho /api/posts/1, @RequestParam cho ?page=2, và nghệ thuật thiết kế URL tử tế. Hẹn gặp lại!

S

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.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9