GraphQL Errors
ในบทเรียนก่อนหน้า เราเห็นแล้วว่า response พก errors array มาด้วยได้ คราวนี้มาแกะ array นั้นกัน ทุก entry คือ object ที่มีชุด key ตายตัว พออ่าน key พวกนี้เป็น คำว่า “มีอะไรพังสักอย่าง” ที่คลุมเครือจะกลายเป็น “field นี้ ตรงตำแหน่งนี้ใน query ของคุณ พังเพราะเหตุนี้”
กายวิภาคของ error entry
หัวข้อที่มีชื่อว่า “กายวิภาคของ error entry”แต่ละ element ใน errors มี key บังคับหนึ่งตัว และ key ที่จะใส่หรือไม่ใส่ก็ได้อีกหลายตัวตามที่ spec กำหนด
message— string ที่คนอ่านรู้เรื่อง อธิบายว่าพังเพราะอะไร นี่คือ key เดียวที่บังคับlocations— รายการตำแหน่ง{ line, column }ชี้ไปยังจุดที่แน่นอนในตัว query text ที่ทำให้เกิด errorpath— รายการชื่อ field และ index ของ list ไล่จาก root ของ response ลงไปถึง field ที่พัง เช่น["currentTrack", "title"]extensions— map ไว้ใส่รายละเอียดแบบเครื่องอ่านได้ และมีบทเรียนถัดไปทั้งบทให้เรื่องนี้
ค่า message มีไว้ให้คนอ่าน ส่วน locations กับ path มีไว้ให้ tooling และ client ที่ต้องเขียนโค้ดตอบสนอง สองตัวนี้รวมกันทำให้ client ไฮไลต์ส่วนที่มีปัญหาใน query และโยนความล้มเหลวไปแสดงถูกที่ใน UI ได้
error สองตระกูล
หัวข้อที่มีชื่อว่า “error สองตระกูล”error ไม่ได้เกิดในขั้นตอนเดียวกันทั้งหมด ตัว spec จึงแบ่งเป็นสองตระกูล ความต่างนี้สำคัญ เพราะเป็นตัวตัดสินว่าคุณจะได้ data กลับมาหรือไม่ได้เลย
flowchart TD Query["Incoming query"] Parse["Parse + validate"] Exec["Execute resolvers"] ReqErr["Request error: errors only, NO data key"] FieldErr["Field error: data present (field null) + errors"] OK["Success: data only"] Query --> Parse Parse -->|"fails"| ReqErr Parse -->|"passes"| Exec Exec -->|"resolver throws"| FieldErr Exec -->|"all resolve"| OK
Request errors เกิด ก่อน execute จะเริ่ม คือ query parse ไม่ผ่าน หรือ parse ผ่านแต่ validate กับ schema ไม่ผ่าน เช่น เจอ field ที่ไม่รู้จัก argument ผิด type หรือ selection ผิดรูป เพราะ execute ไม่ได้เริ่มเลย response จึงไม่มี data key (แม้แต่ null ก็ไม่มี) เหลือแค่ errors เท่านั้น
Field errors (หรือ execution errors) เกิด ระหว่าง execute ตอนที่ resolver throw หรือคืนค่าที่ไม่ถูกต้อง จังหวะนั้น server กำลังประกอบ data อยู่แล้ว response จึงมี data ติดมาด้วย โดย field ที่พังกลายเป็น null และมี errors entry ของ field นั้นแนบมา นี่คือกรณีสำเร็จบางส่วนที่เราจะเจาะลึกในบทเรียนถัดไป
ดู request error เทียบกับ field error
หัวข้อที่มีชื่อว่า “ดู request error เทียบกับ field error”ตัว runner ด้านล่างรัน operation สองอันกับ schema เดียวกัน อันแรกขอ field ที่ไม่มีอยู่จริง จึงตกตั้งแต่ validation กลายเป็น request error ที่ไม่มี data อันที่สองรัน query ที่ถูกต้อง แต่ resolver throw จึงเป็น field error — data กลับมาโดย field ที่พังเป็น null ลองเทียบ output สองก้อนนี้ดู
ดู block แรกก่อน ไม่มี data key มีแค่ errors และ entry นั้นพก locations ชี้ไปตรงจุดที่ ghostField โผล่ใน query ทีนี้ดู block ที่สอง คราวนี้ data มีอยู่ โดย nowPlaying เป็น null และ error ที่คู่กันพก path เป็น ["nowPlaying"] ให้ client รู้แน่ชัดว่า field ไหนหายไป เป็น errors array ตัวเดียวกันแท้ ๆ แต่คนละสถานการณ์ และตัวแยกคือมี data อยู่หรือเปล่า
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| error array ใน response — partial success เป็นไปได้ | error และ data ผสมกันใน response เดียว — client ต้องจัดการทั้งคู่ |
extensions field ให้ metadata เพิ่ม เช่น error code | developer มักลืม check errors array ทำให้ bug ซ่อน |
| schema validation error ชัดเจน — field ไหน type ไหนผิด | HTTP 200 เสมอแม้มี error — monitoring ที่ดู HTTP status อาจไม่รู้ |
| path ใน error บอกว่า field ไหนใน query ที่เกิด error | error format ต่างกันระหว่าง library — ต้อง normalize |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Throw Generic Error ทุกอย่าง
อาการ:
throw new Error("Something went wrong")ใน resolver ทุกจุดที่ error- client ไม่รู้ว่าเกิดอะไรขึ้น — ไม่มี error code หรือ detail
- ใช้ error class ที่ specific:
UserNotFoundError,ValidationErrorพร้อม code ใน extensions
ไม่ Check errors Array ใน Client
อาการ:
- client check แค่ว่า
dataมีค่าหรือไม่ - request สำเร็จบางส่วนแต่ client แสดงผลราวกับสำเร็จทั้งหมด
- check
errorsarray เสมอ แม้dataจะมีค่า
💡 ตัวอย่างจากของจริง
Apollo Server:
- built-in error classes:
AuthenticationError,ForbiddenError,UserInputError- แต่ละ class set HTTP status code และ extensions.code อัตโนมัติ
GitHub:
- error ใน response มี
message,type,pathครบ- client รู้ว่า field ไหนใน query ที่ fail และเหตุผลคืออะไร