JSON Toolkit

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.

Trung cấpĐọc khoảng 4 phútXuất bản 2026-10-30Cập nhật 2026-10-30
Ba dạng phản hồi API và các điểm đánh đổi

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

  1. Endpoint danh sách phải mang metadata phân trang. Không có total hay con trỏ thì không có trình phân trang.
  2. Danh sách rỗng trả [], không bao giờ trả null. null.map là sự cố lặp đi lặp lại.
  3. Timestamp theo ISO 8601 kèm offset. Xem định dạng nào cho timestamp.
  4. Một quy ước đặt tên. snake_case hoặc camelCase, chuyển đổi một lần ở ranh giới.
  5. Tiền dưới dạng số nguyên đơn vị nhỏ nhất hoặc chuỗi. 99.99 không chính xác trong số thực dấu phẩy động nhị phân.
  6. Đừng nhầm lẫn null với việc thiếu trường. Xem null hay 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.

Đọc thêm