โครงสร้าง response ของ API ควรเป็นอย่างไร
สรุปสำคัญ: object เปล่า ๆ สะอาดจนกระทั่งต้องมี metadata; envelope เต็มรูปแบบปลอดภัยแต่ยาว สำหรับโปรเจกต์ส่วนใหญ่ใช้ HTTP status บอกผลลัพธ์และ envelope ที่คงที่สำหรับ data

สรุปสั้น ๆ: บอกผลลัพธ์ด้วย 4xx/5xx status code, ใส่ payload ใน { "data": ..., "meta": ... } และเปลี่ยนเป็น envelope error เมื่อล้มเหลว; เท่านี้พอสำหรับ API ภายใน และเฉพาะแพลตฟอร์มสาธารณะที่มีผู้เชื่อมต่อจำนวนมากเท่านั้นที่ต้องใช้ JSON:API หรือ OpenAPI
ตัดตัวเลือกที่แย่ที่สุดออกก่อน: คืน 200 เสมอโดยบอกผลลัพธ์ในฟิลด์ code
สามรูปร่าง
A. Object เปล่า ๆ
{ "id": 1, "name": "demo", "price": 99 }
สะอาด และ HTTP semantics ทำงานแทน (404 ก็คือ 404) แต่ทันทีที่ต้องมี pagination, warning หรือ trace id ก็ไม่มีที่ใส่หากไม่เปลี่ยนสัญญา
B. Envelope ที่คงที่
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "abc", "took": 12 }
}
Payload และ metadata อยู่แยกกัน metadata ใหม่จึงไม่เปลี่ยนรูปร่างของ data ต้นทุนคือการซ้อนเพิ่มหนึ่งชั้น
C. ทุกอย่างเป็น envelope (หลีกเลี่ยง)
{ "code": 0, "message": "success", "data": {...} }
รูปร่างไม่ใช่บาป การปฏิบัติต่างหากคือบาป status 200 สำหรับทุกผลลัพธ์หมายความว่า CDN, gateway, circuit breaker, นโยบาย retry และ dashboard ทั้งหมดเห็นว่าสำเร็จ ทุก alert จึงต้อง parse แบบเฉพาะ
คำแนะนำ
HTTP status สำหรับผลลัพธ์, envelope สำหรับ payload
{
"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" }
}
Status 4xx หรือ 5xx; code คงที่และเครื่องอ่านได้; message สำหรับมนุษย์; details สำหรับ error ระดับฟิลด์ในฟอร์ม
กฎตายตัว
- Endpoint แบบ list ต้องมี metadata ของ pagination ถ้าไม่มี
totalหรือ cursor ก็ไม่มี pager - List ว่างคืน
[]ไม่ใช่nullnull.mapคือ outage ที่เกิดซ้ำ - Timestamp ใน ISO 8601 พร้อม offset ดู รูปแบบที่ควรใช้กับ timestamp
- รูปแบบการตั้งชื่อเดียว
snake_caseหรือcamelCaseแปลงครั้งเดียวที่ขอบเขต - เงินเป็นจำนวนเต็มหน่วยย่อยหรือสตริง
99.99ไม่แม่นยำใน binary floating point - อย่าปน
nullกับฟิลด์ที่ไม่มี ดูnullหรือไม่ใส่ฟิลด์
เมื่อไหร่ควรใช้ข้อกำหนดมาตรฐาน
- แพลตฟอร์มสาธารณะที่มีผู้เชื่อมต่อจำนวนมาก: JSON:API หรือ OpenAPI พร้อม envelope ที่ใช้ร่วมกัน
- API ภายในของทีมเดียว: envelope ข้างต้นก็พอ
- สัญญาที่เข้มงวด: Schema ก่อน แล้วสร้างโครงสร้างจากมัน
ข้อกำหนดมาตรฐานประหยัดเวลาเถียงและต้นทุนการ onboard สำหรับทีมห้าคน JSON:API มักเป็นการทำงานเชิงลบ
คำถามที่พบบ่อย
ควรเลือกระหว่าง object เปล่า ๆ กับ envelope อย่างไร?
ใช้ envelope เมื่อต้องมี pagination, warning หรือ requestId เพื่อให้ meta ขยายได้โดยไม่เปลี่ยนรูปร่าง data คืน object เปล่า ๆ เฉพาะเมื่อไม่ต้องมี metadata และยอมรับได้ว่าการเพิ่มฟิลด์ภายหลังจะเปลี่ยนสัญญา
ทำไม API ไม่ควรคืน 200 สำหรับทุกผลลัพธ์?
CDN, gateway, circuit breaker, นโยบาย retry และ dashboard ต่างอ่าน HTTP status เพื่อตัดสินว่าจะ cache, retry หรือ alert เมื่อเป็น 200 ทั้งหมด พวกมันจึงถือความล้มเหลวเป็นความสำเร็จ และทุก alert ต้อง parse business code แบบเฉพาะ
Endpoint แบบ list ควรมี metadata ของ pagination ไหม?
ควร ถ้าไม่มี total หรือ cursor ก็ไม่มี pager และ list ว่างต้องคืน [] ไม่ใช่ null มิฉะนั้นการเรียก .map ของ frontend จะพัง