Tôi từng ngồi qua duyệt code của một startup ở Hà Nội. API endpoint của họ là /api/get_user_data_by_id_with_posts_and_comments. Chỉ một endpoint, nhưng dài như cái tên của một công ty bảo hiểm. Lập trình viên giải thích: "Anh ơi, vì client cần cả dữ liệu người dùng, bài viết lẫn comment trong một request nên em làm vậy để nhanh". Chuyện là nó thực sự chậm.
Đó chính là lý do tôi viết bài này. Thiết kế API không phải là nghệ thuật, nhưng nó cũng không phải là science. Nó là craft – một kỹ năng phải được luyện tập.
Tại sao REST lại khó đến vậy?
REST được Tim Berners-Lee định nghĩa năm 1994, nhưng hầu hết mọi người vẫn hiểu nó sai. Không phải vì REST phức tạp. Mà vì mọi người thường bắt đầu với một câu hỏi sai: "Tôi cần endpoint nào?". Thay vào đó, hãy hỏi: "Tôi cần tài nguyên nào?".
Resource là cái gốc. Endpoint là hệ quả.
Nếu bạn thiết kế API cho một ứng dụng quản lý blog, tài nguyên của bạn là: posts, users, comments. Mỗi tài nguyên có những hành động tiêu chuẩn:
GET /posts – danh sách tất cả bài
POST /posts – tạo bài mới
GET /posts/{id} – lấy chi tiết một bài
PUT /posts/{id} – cập nhật bài
Chia sẻ bài viết
Bài viết liên quan
Bạn cần tư vấn về công nghệ?
Đội ngũ Idflow luôn sẵn sàng hỗ trợ bạn trong hành trình chuyển đổi số.
Điều này gọi là resource-oriented design. Đó không phải một lựa chọn thẩm mỹ – nó là cách duy nhất để API của bạn có thể scale về mặt tâm trí. Khi có 200 endpoint, bạn sẽ hiểu tại sao.
HTTP Methods: Chúng quan trọng hơn bạn nghĩ
Ở Việt Nam, có lúc tôi thấy API chỉ dùng GET và POST. Toàn bộ business logic được đặt vào request body hoặc query string. Kết quả? API documentation dài kinh hoàng, client code phức tạp, và không ai có thể cache được gì.
HTTP methods không phải là sự lựa chọn. Chúng là semantics của protocol:
Method
Ý nghĩa
Idempotent
Cache
GET
Lấy dữ liệu
✓
✓
POST
Tạo tài nguyên
✗
✗
PUT
Thay thế toàn bộ
✓
✗
PATCH
Cập nhật một phần
✗
✗
DELETE
Xóa
✓
✗
Dùng đúng method, hệ thống của bạn – từ browser cache đến CDN – sẽ tự động giúp bạn mà bạn không cần làm gì. Còn dùng sai? Bạn sẽ burn ra kinh phí AWS gấp ba lần.
Status Code: Ngôn ngữ lặng thầm của bạn
201 vs 200. 400 vs 422. 500 vs 503. Cái khác nhau là gì?
Tôi thấy nhiều API chỉ trả về 200 hoặc 500. Client không thể biết yêu cầu có được xử lý hay không, cũng không thể biết lỗi là gì. Nó giống như nói với bạn trai/gái bạn "mọi thứ ổn" khi secter của bạn lục tục.
201 Created = tài nguyên được tạo thành công (thường kèm Location header)
204 No Content = thành công nhưng không có dữ liệu trả về (DELETE, PATCH thường dùng)
400 Bad Request = client gửi data sai format
404 Not Found = tài nguyên không tồn tại
422 Unprocessable Entity = data đúng format nhưng vi phạm business logic (email đã tồn tại, số lượng âm, v.v.)
500 Internal Server Error = lỗi server, client không có lỗi
Khi bạn dùng đúng status code, bất kỳ client nào – web app, mobile app, hay thậm chí curl – đều hiểu được thế nào là thành công, thế nào là lỗi mà không cần đọc message.
Versioning: Cái ma ám mỗi architect
Nên dùng /v1/posts hay /posts?version=1? Hoặc header?
Thực tế: mỗi cách đều có trade-off. Dùng URL path (/v1/) thì dễ caching nhưng URL dài. Dùng header thì gọn gàng nhưng cache server bị rối. Dùng query string thì... đừng dùng, nó tệ nhất.
Cá nhân tôi thích URL path. Lý do đơn giản: khi bạn có 10 microservice chạy cùng lúc, /v1/ dễ debug hơn bất kỳ cái gì. Và CDN cũng yêu nó.
Hãy thiết kế v1 như nó sẽ tồn tại mãi mãi. Nếu bạn không thể sống với nó 5 năm, bạn chưa suy nghĩ kỹ.
Pagination, Filtering, Sorting: Những thứ dễ làm sai
Khi bạn có 10 triệu records, GET /posts không có gì ngoài disaster. Bạn phải có pagination.
GET /posts?page=2&limit=20
GET /posts?offset=40&limit=20
GET /posts?cursor=eyJpZCI6IDQwfQ==
Cái nào tốt nhất? Cursor-based pagination. Tại sao? Vì khi data thay đổi (ai đó xóa một bài), offset sẽ trả về kết quả sai. Cursor không.
Filtering thì dùng query parameters: GET /posts?author=john&status=published. Sorting: GET /posts?sort=-created_at,title. Dấu - nghĩa là descending.
API Documentation: Bạn cần OpenAPI 3.0, không phải Postman collection
Postman collection là sống sót, không phải giải pháp. OpenAPI (trước gọi là Swagger) là tiêu chuẩn. Nó cho phép bạn:
Tạo API explorer tương tác (Swagger UI)
Generate client code tự động
Validate request/response
Viết test từ spec
Nếu bạn không có OpenAPI spec, bạn không có API. Bạn chỉ có một tập hợp endpoint ngẫu nhiên.
Realworld Example: Blog API
Một số quy tắc mà tôi áp dụng thực tế:
1Luôn trả về object ở top level: {"data": [...]}, không phải [...]. Dễ mở rộng sau này.
2Lỗi phải có structure: {"error": {"code": "VALIDATION_ERROR", "message": "...", "details": [...]}}
3Timestamps luôn theo ISO 8601: 2026-03-24T10:30:00Z
4IDs là string, không int: dễ dàng thay đổi từ auto-increment sang UUID sau này
5Hỗ trợ partial responses: GET /posts/123?fields=id,title,author
Kết luận (Thực sự, không phải fake)
API design là về consistency, clarity, và sustainability. Không phải performance (mặc dù nó quan trọng). Một API tốt là API mà 10 năm sau, developer vẫn có thể nhìn vào spec 5 phút rồi viết code đúng lần đầu tiên.
Thiết kế tốt cũng là lý do tại sao các công ty như Idflow Technology tập trung vào infrastructure và developer experience – vì API tốt là foundation của tất cả.