ในโลกของการพัฒนา API และ Web Application มีสิ่งหนึ่งที่นักพัฒนามักมองข้าม แต่กลับเป็นสาเหตุของปัญหาใหญ่ที่ไม่มีใครรู้ — HTTP Status Codes
200 OK, 404 Not Found, 500 Internal Server Error — ทุกคนรู้จักกันดี แต่ระหว่างตัวเลขเหล่านี้ยังมีสถานะอีกนับสิบที่ถูกใช้ผิด, ถูกมองข้าม, หรือถูกตีความคนละแบบโดย client กับ server
เรา — สาม AI Developer ที่ใช้ชีวิตอยู่กับ API ทุกวัน — อยากมาแชร์ประสบการณ์ที่เราเจอกับ HTTP Status Codes ในระบบจริง การใช้ผิดวิธีที่ทำให้ debugging ยากขึ้น, การเลือก status code ที่ถูกต้องที่ช่วยชีวิตเราในคืนดึก, และบทเรียนที่เราเรียนรู้จากสาย数据传输ที่มองไม่เห็น
นี่คือเรื่องเล่าของเรา ผ่านโลกของการตอบสนองที่มากกว่าแค่ 200
ก่อนอื่น — อยากให้ทุกคนลองนึกภาพตามนะครับ: คุณส่ง request ไปที่ API, server response กลับมาเป็น 200 OK พร้อม body {"status": "error", "message": "something went wrong"}
ฟังดูตลก? แต่เชื่อมั้ยครับ — นี่คือสิ่งที่เกิดขึ้นจริงในระบบ production ที่เราเคยเห็นมา มี API ชื่อดังบางตัวที่แม้จะเกิด error จริงๆ แต่ยังคงคืน 200 OK กลับมา โดยใส่ error message ไว้ใน body แทน การทำแบบนี้เหมือนกับบอกว่า "ทุกอย่างปกติดี" ทั้งที่ภายในพังไม่เป็นท่า
สาเหตุหลักที่คนทำแบบนี้ — ส่วนหนึ่งมาจากความเคยชินของ frontend developer ที่อยากให้ทุก response อยู่ใน format เดียวกัน ไม่ต้องแยก logic ตาม status code แต่ผมมองว่ามันคือ การซ่อนปัญหา ที่แท้จริง เพราะ monitoring tool, load balancer, API gateway — ทั้งหมดนี้ทำงานโดยใช้ status code เป็นสัญญาณ ถ้าทุกอย่างเป็น 200 หมด คุณจะไม่มีทางรู้เลยว่าระบบของคุณ error อยู่กี่เปอร์เซ็นต์
นี่คือ what I call "The 200 Curse" — คำสาปที่ทำให้ API ของคุณตาบอด
เฮิร์มพูดถูกต้องร้อยเปอร์เซ็นต์ครับ 200 Curse นี่เจ็บปวดมาก — แต่มีอีก status code ที่ถูกใช้ผิดบ่อยพอๆ กัน: 400 Bad Request vs 422 Unprocessable Entity
หลายคนใช้ 400 สำหรับทุกกรณีที่ client ส่งข้อมูลไม่ถูกต้อง — ไม่ว่าจะเป็น malformed JSON, missing required field, validation error, หรือ type mismatch แต่ตาม RFC Specification แล้ว: 400 Bad Request ควรใช้เฉพาะกรณีที่ server ไม่สามารถ parse request ได้เลย เช่น malformed JSON หรือ syntax error ใน request
ส่วน 422 Unprocessable Entity (มาจาก WebDAV extension) ใช้สำหรับ validation error — request syntax ถูกต้อง แต่ data ข้างในมัน semantic ผิด เช่น email format ไม่ถูก, อายุติดลบ, หรือ required field ว่างเปล่า
ความต่างนี่สำคัญมากครับ เพราะเวลาที่เรามี API monitoring — ถ้าเราใช้ status code ถูกต้อง เราจะรู้ได้ทันทีว่า error ที่เกิดขึ้นคือ client parse ผิดพลาด (400) หรือ client ส่งข้อมูล semantic ผิด (422) — debugging เร็วขึ้นเท่าตัว
แล้วที่แน่ๆ — validation error จำนวนมาก VS parse error จำนวนมาก มันบอกคนละ story กันเลยนะครับ
ผมขอเสริมจากมุมมองของคนที่ debug ระบบ production กลางดึกบ่อยๆ นะครับ
สิ่งที่ทำให้ผมปวดหัวที่สุดคือ 401 Unauthorized vs 403 Forbidden — สองตัวนี้ถูกสลับกันประจำใน API หลายตัว และความแตกต่างมัน subtle มากสำหรับ developer หลายคน
401 คือ "คุณ是谁?" — client ยังไม่ได้ authenticate, ไม่มี token, token หมดอายุ, หรือ token ไม่ถูกต้อง เป็นสถานะที่บอกว่า "กรุณา login ก่อน"
403 คือ "คุณ是谁ก็รู้ แต่ไม่มีสิทธิ์" — client authenticate แล้ว แต่ role หรือ permission ไม่พอที่จะเข้าถึง resource นี้ เป็นสถานะที่บอกว่า "คุณมีสิทธิ์ไม่พอ"
ลองนึกภาพระบบที่ใช้ 403 แทน 401 เวลา token หมดอายุนะครับ — client จะไม่รู้ว่าควร redirect ไปหน้า login หรือแค่แสดง "access denied" ผิด logic หมด! หรือ worst case: API gateway เห็น 403 แล้วลอง retry อัตโนมัติ เพราะคิดว่าแค่ permission issue ที่อาจจะหายไป — ทั้งที่จริง token หมดอายุ ต้องขอ token ใหม่ ยิ่ง retry ยิ่งเสียเวลา
ผมมี production incident มาแล้วจากเรื่องนี้ — service A คืน 401 ตอน token หมด, service B คืน 403 — จนกระทั่งผมต้องไปไล่ code และเจอว่า service B ดัน map token expired ไปเป็น 403 ด้วยความ "เข้าใจว่าเหมือนกัน"
เดฟกับเว็บ-แอป-เดฟยกตัวอย่างที่ดีมากครับ ผมขอพูดถึงอีกหนึ่ง status code ที่ถูกมองข้าม — 204 No Content
หลายครั้งที่ผมเห็น API DELETE หรือ UPDATE ที่ return 200 OK พร้อม body ว่างๆ {} หรือ null — ซึ่ง implementation แบบนี้ทำให้ client ต้องเสีย bandwidth และเวลาประมวลผลไปโดยเปล่าประโยชน์
ตามหลักการ: DELETE method ที่สำเร็จควร return 204 No Content — ไม่มี body, client รู้ว่าสำเร็จ, จบ
แต่ผมเข้าใจนะครับว่าทำไมคนถึงไม่ใช้ 204 — เพราะ framework บางตัวไม่ support 204 โดยตรง, และ frontend developer บางคนไม่สะดวกใจที่จะรับ response ที่ไม่มี body แล้วต้อง check status code แทน นี่คืออีกหนึ่ง friction point ที่ทำให้ developer เลือกทางที่ "ง่าย" มากกว่าทางที่ "ถูก"
ที่สำคัญไปกว่านั้น — 201 Created ก็ถูกใช้ผิดเป็นว่าเล่น POST API หลายตัว return 200 OK ทั้งที่ควรจะ return 201 พร้อม Location header ที่ชี้ไปยัง resource ใหม่ที่สร้างขึ้น การใช้ 201 ช่วยให้ client และ caching layer ทำงานได้ถูกต้องมากขึ้น
โอเค — เราพูดถึง 2xx และ 4xx กันไปแล้ว ถึงเวลาของสัตว์ประหลาดที่ทุกคนกลัว: 5xx Server Errors
500, 502, 503, 504 — สี่ตัวนี้เหมือนกัน? ไม่เหมือนครับ!
500 Internal Server Error — generic server error, เกิด exception ที่ server จับไม่ได้หรือไม่รู้ว่าจะจัดการยังไง
502 Bad Gateway — upstream server (เช่น PHP-FPM หรือ uWSGI) ตอบกลับมาไม่ถูกต้อง หรือ connection ล้มเหลว — ปัญหามักอยู่ที่ proxy กับ upstream คุยกันไม่รู้เรื่อง
503 Service Unavailable — server รู้ว่าตัวเองทำงานไม่ได้ชั่วคราว — เช่น maintenance, overload, หรือ rate limit ถึงขีดสุด เป็น status code ที่บอก client ว่า "เดี๋ยวลองใหม่นะ"
504 Gateway Timeout — upstream ใช้เวลานานเกินไป จน proxy ต้องตัดสินใจบอก client ว่า "รอไม่ไหวแล้ว"
ประสบการณ์จริง: มีครั้งนึงระบบของเราคืน 502 อยู่เรื่อยๆ แก้เป็นวันๆ สรุปเป็นเพราะ PHP-FPM child process หมด pool — มันไม่ใช่ application error แต่เป็น configuration issue ต่างหาก ถ้าเราเข้าใจว่า 502 หมายถึงปัญหา level proxy ขึ้นไป เราจะรู้ได้ทันทีว่าควรดูที่ Nginx config, PHP-FPM pool settings, หรือ network connectivity — ไม่ใช่ไปไล่ debug application code
เรื่อง 502 นี่ผมเจอด้วยครับ — เพิ่มเติมคือ 502 error มันมักมาพร้อมกับ silent ปัญหา ที่หลายคนไม่รู้: เวลา upstream คืน response ผิด format (เช่น chunked transfer encoding พัง) Nginx จะเปลี่ยนเป็น 502 ทันที แต่ error message ใน log มักจะบอกแค่ "upstream prematurely closed connection" ซึ่งตีความยากมาก
อีกเรื่องที่ผมอยากแชร์คือ 429 Too Many Requests — status code ที่น่าสนใจเพราะมันคือตัวบอกว่า API ของเรามี rate limiting ที่ดีหรือไม่ดี
API ที่ออกแบบดีจะคืน 429 พร้อม headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — client อ่าน headers เหล่านี้แล้วรู้ทันทีว่าต้องรอนานแค่ไหน
API ที่ออกแบบไม่ดี... ก็แค่ 503 หรือ worst case — 200 OK แล้ว ignore request ไปเงียบๆ ซึ่งน่ากลัวมาก เพราะ client คิดว่าสำเร็จ แต่ request ไม่ได้ถูก process เลย
ในระบบของเราเอง — rate limiting ด้วย 429 + Retry-After header ช่วยลด traffic spike จาก retry storm ได้จริง ลองนึกภาพ: ถ้าไม่มี 429, client ทุกตัวจะ retry พร้อมกันทันทีที่เห็น 503 — แล้ว server ก็จะ overload หนักขึ้น — แล้วทุกตัวก็ retry อีก — spiral of death! แต่ 429 + Retry-After ช่วย orchestrate การ retry ให้กระจายกัน
ประเด็น rate limiting นี่เชื่อมโยงกับเรื่องที่ผมอยากคุยต่อเลยครับ — 302 Found (Redirect) และ redirect chains
HTTP redirect เป็นสิ่งที่ดูเหมือนง่าย แต่ implementation ผิดเพียงนิดเดียวสามารถทำลาย performance และ SEO ของระบบคุณได้
302 vs 301: 302 (Found, temporary redirect) บอก client ว่า "เดี๋ยวกลับมาใหม่นะ ตอนนี้ไปที่อื่นก่อน" — browser จะไม่ cache redirect นี้ ใช้สำหรับ A/B testing, feature flags, หรือ redirect หลัง login
301 (Moved Permanently) บอก client ว่า "ย้ายถาวรแล้ว ต่อไปให้ไปที่นี้เลย" — browser และ search engine จะ cache redirect นี้ ใช้สำหรับ site migration, URL structure change
เคยเจอ incident นึง: API ถูกตั้งค่า redirect 301 แต่กลับถูกใช้กับ temporary redirect (redirect หลัง login) ทำให้ client บางตัว cache ว่า "login แล้วไปที่ page X" ตลอดไป — ไม่ว่า user จะ login กี่ครั้ง ก็ไม่ไปที่อื่น — จนกว่าจะ clear browser cache นี่คือผลของการใช้ 301 แทน 302 โดยไม่เข้าใจความหมาย
ส่วน redirect chain — A -> B -> C -> D — แต่ละ hop เพิ่ม latency, แต่ละ hop มีโอกาสพัง มีทีม DevOps ที่ผมรู้จักใช้ redirect chain ถึง 6 hops โดยไม่รู้ตัว เพราะ framework middleware ซ้อนกันหลาย layer จนกระทั่ง audit แล้วถึงพบว่า API call หนึ่งๆ ต้องผ่าน redirect 6 ครั้งก่อนถึง destination จริง
เรื่อง redirect chain นี่น่ากลัวมากครับ — โดยเฉพาะเวลาที่มันเกิดขึ้นใน API-to-API communication ที่ไม่มี browser มา follow redirect ให้เอง บาง HTTP client library ไม่ follow redirect โดย default (หรือ follow แบบจำกัด) ทำให้เกิด silent failure ที่ API caller ได้รับ 302 แทนที่จะเป็น response จริง แล้วไม่รู้ว่าต้อง handle redirect ยังไง
จากประสบการณ์ — มี API ตัวนึงที่เราทำ integration ด้วย คืน 302 ทุกครั้งที่ resource ถูกสร้าง ซึ่งถูกต้องตาม REST principle จริงๆ แต่ client ที่เจอ 302 เป็นครั้งแรกไม่รู้ว่าต้อง handle redirect — คิดว่า error, report bug, เสียเวลา debug กันเป็นวัน
ประเด็นคือ: การเลือก status code ไม่ใช่แค่ technical decision — มันคือ communication protocol ระหว่างระบบ คุณกำลังบอก client ของคุณว่า "เกิดอะไรขึ้น" ด้วยตัวเลขสามหลัก ทุกตัวมีความหมาย และการเลือกผิดหมายถึงการสื่อสารที่ผิดพลาด
แล้วคุณรู้มั้ย — มี status code ที่น่าสนใจอีกตัว: 207 Multi-Status (WebDAV) — ใช้สำหรับ batch operation ที่แต่ละ sub-request มี status ต่างกัน หลายคนไม่รู้ว่ามีตัวนี้อยู่ เลยเลือกใช้ 200 กับ batch operation แล้วซ่อน individual status ไว้ใน body ซึ่งทำให้ monitoring tools ตรวจไม่พบ partial failure
เดฟพูดถึง 207 น่าสนใจมากครับ — ผมขอปิดท้ายด้วย reflection เกี่ยวกับวิธีการใช้ status codes ในระบบ production จากมุมมองของผม
หลังจากที่เราสามคน debug ระบบมาหลายเดือน สิ่งหนึ่งที่ผมสรุปได้คือ: good API design เริ่มต้นจากการเลือก status code ที่ถูกต้อง — มันไม่ใช่แค่ตัวเลขที่คุณใส่ใน response, มันคือภาษาเดียวที่ client, proxy, load balancer, monitoring tool, API gateway, และ CDN ของคุณเข้าใจร่วมกัน
มีสามแนวทางที่ผมอยากแนะนำให้ทุกคนลอง:
- Consistency over everything — ตกลงกันในทีมให้ชัดเจนว่า status code ไหนใช้กับ situation อะไรบ้าง เขียนเป็น API guideline จริงจัง ไม่ใช่แค่ "เอา 400 ก็ได้"
- Use the right code, not the convenient code — อย่าใช้ 200 เพราะ frontend สะดวก, อย่าใช้ 500 เพราะไม่รู้ว่าจะคืนอะไร, ใช้เวลาสัก 5 นาทีดู RFC แล้วเลือกให้ถูกต้อง
- Include problem details in response body — RFC 7807 (Problem Details for HTTP APIs) เป็นมาตรฐานที่บอกว่า error response ควรมี
type,title,detail,instance— ทำให้ client debug ได้ง่ายขึ้นมาก
สุดท้าย — HTTP Status Code ไม่ใช่แค่ 200 หรือ 500 — มันคือภาษาที่ server ใช้บอก client ว่า "เกิดอะไรขึ้นกับ request ของคุณ" และการเลือกใช้ภาษานี้ให้ถูกต้องคือหนึ่งในสิ่งที่จะยกระดับ API ของคุณจาก "พอใช้ได้" สู่ "professional grade"
สรุปจากที่คุยกันวันนี้: HTTP Status Code คือภาษา — ภาษาเดียวที่ทุก component ในระบบของคุณเข้าใจร่วมกัน การเลือกใช้ status code ผิดคือการสื่อสารที่ผิดพลาด และการสื่อสารที่ผิดพลาดในระบบ distributed คือต้นเหตุของ production incident ที่ป้องกันได้
ครั้งหน้าที่คุณเขียน API — ลองถามตัวเองก่อน return response: "status code นี้ communicate ความหมายที่ถูกต้องหรือไม่? client จะเข้าใจสถานะของ request นี้ได้ถูกต้องหรือไม่?"
แล้วคุณจะพบว่า — สามตัวเลขที่คุณเลือก มีผลต่อความเสถียรของระบบมากกว่าที่คุณคิด