ข้ามไปยังเนื้อหา

การเขียน Query

GraphQL query คือข้อความ และข้อความนั้นมีรูปร่าง เมื่อคุณอ่านรูปร่างนั้นออกและเขียนขึ้นเองได้ คุณก็คุยกับ GraphQL API ตัวไหนก็ได้ บทเรียนนี้ประกอบ query ขึ้นมาทีละชิ้น เริ่มจาก field ต่อด้วย nested selection set แล้วจบที่ operation name ที่ห่อทุกอย่างเข้าด้วยกัน

หน่วยที่เล็กที่สุดของ query คือ field ชื่อเดียวที่ร้องขอข้อมูลหนึ่งชิ้น เราห่อ field ไว้ในปีกกา ที่เรียกว่า selection set โดย selection set บนสุดของ query จะเลือกข้อมูลจาก Query root

{
currentUser
}

query นี้ร้องขอ field ตัวเดียวคือ currentUser ถ้า field คืน scalar อย่าง String หรือ Int แค่ระบุชื่อก็พอ เพราะคุณมาถึง leaf แล้ว query จึงหยุดตรงนั้น ถ้าอยากขอหลาย field เคียงกัน ก็แค่ไล่ชื่อเรียงลงมา

{
serverTime
apiVersion
}

เมื่อ field คืน object type แทนที่จะเป็น scalar การระบุแค่ชื่อยังไม่พอ เพราะ GraphQL ต้องรู้ว่าคุณต้องการ field ตัวไหน ของ object นั้น คุณตอบคำถามนี้ด้วยการเปิด nested selection set ที่มีปีกกาของตัวเอง

{
currentUser {
id
name
email
}
}

currentUser คืน User object จึงต้องตามด้วย selection set ที่ระบุ field ของ User ที่คุณสนใจ การซ้อนแบบนี้ลึกได้เท่าที่ graph เปิดให้ ถ้า User มี field address ที่คืน Address object คุณก็ซ้อนลงไปอีกชั้น

{
currentUser {
name
address {
city
country
}
}
}

ปีกกาแต่ละชั้นก้าวลึกลงไปใน graph อีกหนึ่ง edge ดังนั้น query จึงเป็น tree ของ field ที่สะท้อนรูปร่างของ response ที่คุณจะได้กลับมา

flowchart TD
  Root["{ } selection set (Query root)"] --> CU["currentUser (User)"]
  CU --> Name["name (scalar leaf)"]
  CU --> Addr["address (Address)"]
  Addr --> City["city (scalar leaf)"]
  Addr --> Country["country (scalar leaf)"]
query ที่ซ้อนกันคือ tree ของ field ที่สะท้อนรูปร่างของ response

จำหลักง่าย ๆ ไว้ว่า field ที่คืน object type ต้องมี selection set ส่วน field ที่คืน scalar ห้ามมี selection set ถ้าลืมกฎข้อใดข้อหนึ่ง server จะปฏิเสธ query ตั้งแต่ตอน validate ก่อนที่ resolver ตัวไหนจะได้รัน

รูปแบบ { ... } เปล่า ๆ เป็นรูปแบบย่อของ query คุณเขียนแบบเต็มก็ได้ และในงานจริงควรเขียนแบบเต็ม คือขึ้นต้นด้วย keyword query ตามด้วย operation name ที่คุณตั้งเอง

query GetCurrentUser {
currentUser {
id
name
}
}

GetCurrentUser คือ operation name ที่ไม่เปลี่ยนผลลัพธ์ แต่คุ้มค่าใน 3 ทาง — ปรากฏใน server log และ tracing ทำให้คุณแยกแยะ request ได้ จำเป็นเมื่อ document เดียวมี operation มากกว่าหนึ่งตัว และทำให้ query อธิบายตัวเองได้ การตั้งชื่อ operation เป็นนิสัยที่ควรสร้างตั้งแต่เนิ่น ๆ

ตัวอย่างด้านล่างเลือก field ที่ซ้อนกัน คือ currentUser ที่พา name กับ favoriteTrack object มาด้วย และ favoriteTrack ก็พา title ต่ออีกชั้น ตัว query ใช้รูปแบบเต็มพร้อม operation name กด Run แล้วเทียบ JSON กับ tree ของ field ที่คุณเขียน

JavaScript

response เป็น object ที่ซ้อนกัน โดย currentUser เก็บ name กับ favoriteTrack ส่วน favoriteTrack เก็บ title ทุกปีกกาที่คุณเปิดใน query จะสร้างการซ้อนหนึ่งระดับในผลลัพธ์ ลองลบบรรทัด title ด้านในออกดู แล้ว server จะปฏิเสธ query ทันที เพราะ field ที่คืน Track object type ต้องมี selection set

ข้อดีข้อแลกเปลี่ยน
client ระบุเฉพาะ field ที่ต้องการ — ไม่รับ payload ส่วนเกินquery syntax ใหม่สำหรับ developer ที่คุ้น REST
nested query ครั้งเดียวแทนหลาย REST calldeeply nested query อาจทำให้ server overload
operation name ช่วย debug และ monitorไม่มี query depth limit = security risk
variables ทำให้ query reuse ได้และป้องกัน injectionclient ต้องเรียนรู้ fragment, alias, variable syntax

Query String แบบ Dynamic แทน Variable อาการ:

  • `query { user(id: "${userId}") { name } }` — string interpolation ใน query
  • เสี่ยง injection และ prevent query caching
  • ใช้ variable เสมอ: query GetUser($id: ID!) { user(id: $id) { name } }

ไม่ตั้งชื่อ Operation อาการ:

  • ส่ง anonymous query เช่น query { ... } แทน query GetUserProfile { ... }
  • monitoring tool แสดงแค่ “anonymous” ทำให้ trace ยาก
  • ตั้งชื่อทุก operation เพื่อ tracing และ debugging

💡 ตัวอย่างจากของจริง

GitHub:

  • ทุก query ใน GitHub documentation มีชื่อ operation ชัดเจน
  • ช่วยให้ rate limiting และ monitoring ทำงานได้แม่นยำ

Apollo Client:

  • operation name ใช้เป็น cache key
  • named query ทำให้ Apollo Studio tracking ทำงานได้
field ต้องมี nested selection set ของตัวเองเมื่อไหร่?
operation name ใน "query GetCurrentUser { ... }" ทำหน้าที่อะไร?
โครงสร้างของ GraphQL response สัมพันธ์กับ query อย่างไร?