Một API tốt giống như một giao diện người dùng tốt: người dùng đầu tiên nhìn vào là hiểu, không cần đọc tài liệu dài dòng hay đoán mò. Với REST API, "người dùng" là các lập trình viên front-end, mobile hay đối tác tích hợp. Khi API đặt tên lộn xộn, trả về mã lỗi tùy hứng và phân trang mỗi chỗ một kiểu, chi phí giao tiếp giữa các nhóm sẽ tăng lên rất nhanh. Bài viết này tổng hợp những quy ước mà nhiều đội ngũ, trong đó có Besoft, áp dụng để giữ API nhất quán và dễ dùng.
Đặt tên endpoint theo tài nguyên
Nguyên tắc nền tảng của REST là URL đại diện cho tài nguyên (danh từ), còn hành động được thể hiện qua phương thức HTTP.
- Dùng danh từ số nhiều:
/products,/orders,/users. - Không đưa động từ vào URL: tránh
/getProductshay/createOrder. - Dùng chữ thường, phân tách bằng dấu gạch ngang:
/order-itemsthay vì/orderItems. - Thể hiện quan hệ bằng đường dẫn lồng nhau nhưng đừng quá sâu:
/orders/42/itemslà hợp lý, còn bốn, năm cấp lồng nhau thì nên tách ra. - Dùng query string cho lọc, sắp xếp, tìm kiếm:
/products?category=laptop&sort=-price.
Kết hợp với phương thức HTTP, ta có bộ thao tác quen thuộc:
GET /products Lấy danh sách sản phẩm
POST /products Tạo sản phẩm mới
GET /products/15 Lấy chi tiết sản phẩm 15
PATCH /products/15 Cập nhật một phần sản phẩm 15
PUT /products/15 Thay thế toàn bộ sản phẩm 15
DELETE /products/15 Xóa sản phẩm 15Với những thao tác không khớp CRUD, như "gửi lại email xác nhận", có thể coi hành động đó là một tài nguyên con, ví dụ POST /users/7/verification-emails. Cách này giữ được tinh thần REST mà vẫn rõ nghĩa.
Chọn đúng HTTP status code
Status code là cách nhanh nhất để client biết chuyện gì đã xảy ra. Trả về 200 kèm { "error": true } trong body là thói quen nên bỏ, vì nó khiến thư viện HTTP, công cụ giám sát và cache đều hiểu sai. Những mã nên dùng thường xuyên:
200 OK: thành công, có dữ liệu trả về.201 Created: tạo mới thành công, nên kèm headerLocationtrỏ tới tài nguyên vừa tạo.204 No Content: thành công nhưng không có body, thường dùng choDELETE.400 Bad Request: request sai cú pháp hoặc tham số không hợp lệ.401 Unauthorized: chưa xác thực hoặc token không hợp lệ.403 Forbidden: đã xác thực nhưng không đủ quyền.404 Not Found: tài nguyên không tồn tại.409 Conflict: xung đột trạng thái, chẳng hạn email đã được đăng ký.422 Unprocessable Content: dữ liệu đúng định dạng nhưng vi phạm quy tắc nghiệp vụ hoặc validation.429 Too Many Requests: vượt giới hạn tần suất gọi.500 Internal Server Error: lỗi phía server mà client không thể tự khắc phục.
Bên cạnh status code, hãy thống nhất một định dạng body cho lỗi trên toàn hệ thống. Chuẩn RFC 9457 (Problem Details for HTTP APIs) là một lựa chọn tốt nếu bạn chưa có quy ước riêng:
{
"type": "https://api.example.com/errors/validation",
"title": "Dữ liệu không hợp lệ",
"status": 422,
"detail": "Trường email không đúng định dạng"
}Phân trang: offset hay cursor?
Không bao giờ nên trả về toàn bộ một bảng lớn trong một response. Có hai chiến lược phân trang phổ biến.
Phân trang theo offset
Client gửi ?page=3&limit=20 hoặc ?offset=40&limit=20. Cách này dễ hiểu, dễ làm giao diện có số trang và cho phép nhảy thẳng tới trang bất kỳ. Nhược điểm là truy vấn chậm dần khi offset lớn, và nếu dữ liệu được thêm hoặc xóa giữa hai lần gọi, người dùng có thể thấy bản ghi bị lặp hoặc bị sót.
Phân trang theo cursor
Server trả về một con trỏ (thường là giá trị đã mã hóa của khóa sắp xếp cuối cùng), client gửi lại nó để lấy trang tiếp theo: ?cursor=eyJpZCI6MTIwfQ&limit=20. Cách này ổn định khi dữ liệu thay đổi liên tục và hiệu năng tốt với bảng lớn, rất hợp với feed hay cuộn vô hạn. Đổi lại, client không nhảy thẳng tới trang 50 được.
Dù chọn cách nào, hãy trả kèm thông tin điều hướng để client không phải tự tính:
{
"data": [],
"pagination": { "limit": 20, "nextCursor": "eyJpZCI6MTQwfQ", "hasMore": true }
}Đồng thời đặt giới hạn limit tối đa phía server để tránh ai đó yêu cầu hàng chục nghìn bản ghi một lần.
Những quy ước nhỏ nhưng đáng giá
- Đánh phiên bản API ngay từ đầu, ví dụ
/v1/products, để có đường lui khi cần thay đổi không tương thích. - Thống nhất kiểu đặt tên trường JSON (camelCase hoặc snake_case) và giữ nguyên trên toàn hệ thống.
- Dùng định dạng ISO 8601 cho ngày giờ, kèm múi giờ rõ ràng.
- Viết tài liệu bằng OpenAPI để có thể sinh client, mock server và kiểm thử tự động.
Kết luận
Thiết kế REST API chuẩn không đòi hỏi kỹ thuật cao siêu mà đòi hỏi sự nhất quán. Đặt tên theo tài nguyên, dùng đúng phương thức và status code, chọn chiến lược phân trang phù hợp với dữ liệu, rồi ghi lại tất cả thành quy ước chung cho cả nhóm. Đầu tư vài giờ thống nhất ngay từ đầu sẽ tiết kiệm hàng tuần trao đổi và sửa lỗi về sau.






