Types และ Fields
object type คือหัวเรี่ยวหัวแรงของ GraphQL schema เกือบทุกอย่างที่ client สัมผัสได้คือ object type ที่มี field และวิธีที่คุณเลือก field เหล่านั้น — ทั้งชื่อ, type และ argument ที่รับ — คือเนื้อแท้ส่วนใหญ่ของการออกแบบ schema บทเรียนนี้ลงลึกเรื่อง object type และสี่สิ่งที่คุณทำกับ field ได้
Object type และ field
หัวข้อที่มีชื่อว่า “Object type และ field”object type คือกลุ่มของ field ที่มีชื่อกำกับ แต่ละ field มีทั้งชื่อและ type ตัวอย่างด้านล่างคือ type ที่จำลองสูตรอาหารในแอปทำอาหาร:
type Recipe { id: ID! title: String! servings: Int! prepMinutes: Int}Recipe มีสี่ field โดยสามตัว resolve เป็น scalar — ID หนึ่งตัว String หนึ่งตัว และ Int สองตัว ส่วน type ของแต่ละ field คือคำสัญญาว่า client จะได้อะไรกลับไป ชื่อ field ควรอ่านแล้วเหมือนคุณสมบัติของสิ่งนั้น เช่น title, servings, prepMinutes เลือกคำนามใน domain ที่ชัดเจนแทนตัวย่อ เพราะคนอ่าน schema บ่อยกว่าเขียนมาก
Argument ของ field
หัวข้อที่มีชื่อว่า “Argument ของ field”field รับ argument ได้เหมือน parameter ของฟังก์ชัน ตัว argument ช่วยให้ field เดียวรองรับคำขอได้หลายรูปแบบ การใช้งานคลาสสิกคือ field สำหรับค้นหาบน root type Query:
type Query { recipe(id: ID!): Recipe recipes(first: Int, search: String): [Recipe!]!}recipe รับ id แบบบังคับ แล้วคืนสูตรอาหารหนึ่งรายการ (หรือ null ถ้าไม่มีรายการไหนตรง) ส่วน recipes รับ argument first กับ search แบบ optional ไว้แบ่งหน้าและกรองรายการ ทั้งนี้ argument ไม่ได้จำกัดอยู่แค่ field ระดับ root — field ไหนก็รับได้ รูปแบบที่พบบ่อยคือให้ field ปรับรูปร่างผลลัพธ์ของตัวเอง:
type Recipe { id: ID! title: String! description(maxLength: Int): String}ตรงนี้ client ขอ description แบบตัดสั้นได้ด้วยการส่ง maxLength มา แล้ว resolver ก็ตัดให้ สังเกตว่า argument วางอยู่บน field ที่ใช้งานจริง
Description — เอกสารใน schema
หัวข้อที่มีชื่อว่า “Description — เอกสารใน schema”GraphQL มีระบบเอกสารมาให้ในตัว ทุก type ทุก field ทุก argument ใส่ description ได้ด้วยการเขียน string literal ไว้เหนือบรรทัดนั้น ตัว description จะโผล่ในเครื่องมือต่าง ๆ ทั้งใน GraphiQL และในเอกสารที่ generate ออกมา:
"""A single dish a user can cook, including timing and serving information."""type Recipe { "Stable unique identifier for the recipe." id: ID!
"Human-readable name shown in listings and headers." title: String!
"How many portions the recipe yields." servings: Int!}บล็อกที่ใช้เครื่องหมายคำพูดสามตัวรองรับข้อความหลายบรรทัด ส่วน string เครื่องหมายคำพูดเดี่ยวก็พอสำหรับบรรทัดเดียว description ไม่ใช่เครื่องประดับ แต่เป็นส่วนหนึ่งของ contract ฝั่งที่มนุษย์อ่าน เขียนเหมือนกำลังอธิบาย field ให้ developer ที่ไม่เคยเห็น domain ของคุณมาก่อนฟัง
ความสัมพันธ์ — วิธีที่ type เชื่อมโยงกันเป็นกราฟ
หัวข้อที่มีชื่อว่า “ความสัมพันธ์ — วิธีที่ type เชื่อมโยงกันเป็นกราฟ”ในวินาทีที่ type ของ field เป็น object type อีกตัวหนึ่ง schema ของคุณก็เลิกเป็นรายการของระเบียนแบน ๆ และกลายเป็น กราฟ สูตรอาหารมีผู้แต่ง ผู้แต่งมีสูตรอาหาร:
type Recipe { id: ID! title: String! author: Author!}
type Author { id: ID! name: String! recipes: [Recipe!]!}field author บน Recipe ชี้ไปที่ type Author และ field recipes บน Author ก็ชี้กลับมา client จึงเริ่มจากสูตรอาหาร ก้าวเข้าไปหาผู้แต่ง แล้วเดินต่อไปยังสูตรอื่น ๆ ของผู้แต่งคนนั้นได้ในการ query ครั้งเดียว field ที่เชื่อมโยงแบบนี้คือ edge ของกราฟ และการเลือก edge ให้ดีคือสิ่งที่ทำให้ API รู้สึกเป็นเนื้อเดียวกัน
flowchart LR Query["Query"] Recipe["type Recipe"] Author["type Author"] Args["Field arg: recipe(id: ID!)"] Query --> Args Args --> Recipe Recipe -->|"author"| Author Author -->|"recipes"| Recipe
ประกอบเข้าด้วยกัน รันได้จริง
หัวข้อที่มีชื่อว่า “ประกอบเข้าด้วยกัน รันได้จริง”ตัวอย่างด้านล่างนิยาม Recipe และ Author ให้ field recipe มี argument และรัน query ที่เดินผ่านความสัมพันธ์ระหว่างสอง type นี้ กดปุ่ม Run
query ส่ง id เป็น argument ได้รับ Recipe หนึ่งรายการ จากนั้นก้าวเข้าไปใน author และออกมายังรายการสูตรอาหารของผู้แต่งคนนั้น หนึ่งรอบการสื่อสาร หลาย type — นั่นคือกราฟที่กำลังทำงาน
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| type system เป็น living documentation ที่ accurate เสมอ | schema ใหญ่ที่ไม่ organize ดีอ่านยาก |
rename field โดยใช้ @deprecated แทนการลบ — backward compat | type ที่ leak database detail ทำให้ refactor ยาก |
| field-level description ทำให้ introspection เป็น API doc | type ที่ generic เกิน เช่น JSON ลด type safety |
non-null ! บังคับ client จัดการ null อย่างถูกต้อง | null vs non-null design ผิด ทำให้ client ต้อง null check ที่ไม่จำเป็น |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Type ที่สะท้อน Database Schema ตรง ๆ อาการ:
- GraphQL type ชื่อ
users_tableหรือ field ชื่อuser_idแทนuserId - expose column name จาก database ให้ client โดยตรง
- schema ควรสะท้อน business domain ไม่ใช่ database implementation
JSON Scalar ทุกที่ที่ไม่รู้จะทำยังไง
อาการ:
metadata: JSONแทนที่จะออกแบบ type ให้ชัดเจน- client ไม่รู้ว่า metadata มี field อะไร — ไม่มี type checking
- หากยังไม่รู้ shape ใช้
JSONชั่วคราวได้ แต่วางแผน migrate ไป typed field
💡 ตัวอย่างจากของจริง
GitHub:
- ใช้
@deprecated(reason: "...")แทนการลบ field- migration period — field ยังคง serve ต่อไป แต่ client รู้ว่าต้องอัปเดต
Shopify:
- schema design ยึด “client first” — field ทุกตัวออกแบบจาก client use case ไม่ใช่ database