Một phản hồi API nên dùng cấu trúc nào
Kết luận: Object trần thì sạch cho đến khi bạn cần metadata; envelope đầy đủ thì an toàn nhưng dài dòng. Với hầu hết dự án, hãy dùng mã trạng thái HTTP cho kết quả và một envelope ổn định cho dữ liệu.

Tóm lại: hãy thể hiện kết quả bằng mã trạng thái 4xx/5xx, mang dữ liệu trong { "data": ..., "meta": ... }, và chuyển sang envelope error khi thất bại; như vậy là đủ cho API nội bộ, và chỉ những nền tảng công khai có nhiều bên tích hợp mới cần JSON:API hay OpenAPI.
Trước hết hãy loại bỏ phương án tệ nhất: luôn trả 200 và đặt kết quả vào trường code.
Ba dạng
A. Object trần
{ "id": 1, "name": "demo", "price": 99 }
Sạch, và ngữ nghĩa HTTP làm phần việc của nó (404 là 404). Nhưng ngay khi bạn cần phân trang, cảnh báo hay trace id, không có chỗ nào để đặt chúng mà không đổi hợp đồng.
B. Envelope ổn định
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "abc", "took": 12 }
}
Dữ liệu và metadata tách khỏi nhau, nên metadata mới không bao giờ làm đổi hình dạng data. Cái giá là thêm một cấp lồng nhau.
C. Envelope toàn phần (tránh)
{ "code": 0, "message": "success", "data": {...} }
Hình dạng không phải là tội; cách làm mới là. Trả trạng thái 200 cho mọi kết quả nghĩa là CDN, gateway, cầu dao, chính sách thử lại và bảng điều khiển đều thấy thành công. Khi đó mọi cảnh báo đều cần phân tích tùy biến.
Khuyến nghị
Mã trạng thái HTTP cho kết quả, envelope cho dữ liệu.
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "req_8f2a", "took": 12 }
}
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order does not exist or was deleted",
"details": [{ "field": "id", "issue": "not_found" }]
},
"meta": { "requestId": "req_8f2a" }
}
Trạng thái 4xx hoặc 5xx; code ổn định và máy đọc được; message cho con người; details cho lỗi form ở mức trường.
Quy tắc cứng
- Endpoint danh sách phải mang metadata phân trang. Không có
totalhay con trỏ thì không có trình phân trang. - Danh sách rỗng trả
[], không bao giờ trảnull.null.maplà sự cố lặp đi lặp lại. - Timestamp theo ISO 8601 kèm offset. Xem định dạng nào cho timestamp.
- Một quy ước đặt tên.
snake_casehoặccamelCase, chuyển đổi một lần ở ranh giới. - Tiền dưới dạng số nguyên đơn vị nhỏ nhất hoặc chuỗi.
99.99không chính xác trong số thực dấu phẩy động nhị phân. - Đừng nhầm lẫn
nullvới việc thiếu trường. Xemnullhay bỏ trường đi.
Khi nào nên dùng một đặc tả
- Nền tảng công khai có nhiều bên tích hợp: JSON:API hoặc OpenAPI kèm envelope chung
- API nội bộ của một nhóm: envelope ở trên là đủ
- Hợp đồng chặt chẽ: Schema trước, cấu trúc sinh ra từ đó
Đặc tả giúp bớt tranh luận và giảm chi phí tiếp nhận. Với một nhóm năm người, JSON:API thường là việc làm thêm âm giá trị.
Hỏi đáp
Nên chọn object trần hay envelope thế nào?
Dùng envelope khi bạn cần phân trang, cảnh báo hoặc requestId, để meta có thể lớn lên mà không làm đổi hình dạng data. Chỉ trả object trần khi bạn không cần metadata nào và chấp nhận rằng thêm trường về sau sẽ đổi hợp đồng.
Tại sao API không bao giờ nên trả 200 cho mọi kết quả?
CDN, gateway, cầu dao, chính sách thử lại và bảng điều khiển đều đọc mã trạng thái HTTP để quyết định có lưu đệm, thử lại hay cảnh báo không. Với 200 ở mọi nơi, chúng coi thất bại là thành công, và mọi cảnh báo đều cần phân tích tùy biến mã nghiệp vụ.
Endpoint danh sách có nên mang metadata phân trang không?
Có. Không có total hay con trỏ thì không có trình phân trang, và danh sách rỗng phải trả [] thay vì null, nếu không lời gọi .map của frontend sẽ hỏng.