Scalars และ Enums
ทุก query ไม่ว่าจะลึกแค่ไหน สุดท้ายก็ไปจบที่ leaf คือค่ารูปธรรมค่าเดียว leaf พวกนั้นคือ scalar กับ enum ที่เป็นชิ้นส่วนเล็กที่สุดของ schema แต่การเลือกให้ดีคือสิ่งที่ทำให้ field อธิบายตัวเองได้และใช้ผิดยาก บทนี้ครอบคลุม scalar แบบ built-in, custom scalar และ enum — เครื่องมือที่คมที่สุดสำหรับชุดตัวเลือกที่ตายตัว
scalar แบบ built-in
หัวข้อที่มีชื่อว่า “scalar แบบ built-in”GraphQL มาพร้อม scalar type ห้าแบบ ทุก schema ใช้ได้เลยโดยไม่ต้องประกาศอะไร:
Int— จำนวนเต็มแบบมีเครื่องหมายขนาด 32 บิต เหมาะกับการนับ ปริมาณ และจำนวนเต็มน้อย ๆFloat— จำนวนทศนิยมแบบมีเครื่องหมายความละเอียดสองเท่า (double-precision) เหมาะกับราคา คะแนน การวัดค่าต่าง ๆString— ข้อความ UTF-8Boolean—trueหรือfalseID— ตัวระบุที่ไม่ซ้ำ (unique identifier) serialize เป็น string แต่ ทึบเชิงความหมาย (semantically opaque) คือมีไว้ใช้เป็นคีย์ ไม่ใช่เอาไป parse หรือคำนวณ
ความต่างระหว่าง ID กับ String สำคัญ แม้ทั้งคู่จะส่งกันในรูปข้อความ การประกาศ field เป็น ID บอกผู้อ่านทุกคนว่า “นี่คือตัวระบุ ให้ปฏิบัติเหมือน token” ที่เป็นคำสัญญาคนละแบบกับ “นี่คือข้อความที่มนุษย์อ่าน”
type Product { id: ID! name: String! priceUSD: Float! stock: Int! inStock: Boolean!}Custom scalar
หัวข้อที่มีชื่อว่า “Custom scalar”ห้าแบบที่ built-in มาไม่ได้ครอบคลุมทุกอย่าง ไม่มี date type ในตัว ไม่มี email type ไม่มี URL type เมื่อค่าหนึ่งมีรูปแบบและกฎการตรวจสอบของตัวเอง คุณสามารถประกาศ custom scalar ได้:
scalar DateTime
type Product { id: ID! name: String! releasedAt: DateTime!}การประกาศ scalar DateTime แค่เพิ่มชื่อนั้นเข้าไปใน schema จากนั้น server จะแนบตรรกะที่ serialize และตรวจค่านั้นเอง (โดยทั่วไปคือ string แบบ ISO-8601 เช่น 2026-06-25T10:00:00Z) custom scalar รวมศูนย์รูปแบบไว้ที่เดียว ทุก field ที่เป็น DateTime จึง parse และตรวจแบบเดียวกันหมด client เลยรู้เสมอว่าจะเจอรูปร่างแบบไหน custom scalar ที่นิยมใช้ก็มี DateTime, EmailAddress, URL และ JSON หยิบมาใช้เมื่อไหร่ก็ตามที่การใช้ String จะบังคับให้ทุก client ต้องเขียนกฎ parse เดิมซ้ำขึ้นมาเอง
Enums — ชุดของค่าที่มีชื่อแบบตายตัว
หัวข้อที่มีชื่อว่า “Enums — ชุดของค่าที่มีชื่อแบบตายตัว”enum คือ type ที่ค่าต้องเป็นหนึ่งในชุดที่มีชื่อและตายตัว เมื่อ field หนึ่งเป็นได้แค่ตัวเลือกที่รู้จักไม่กี่ตัว enum ก็มัดเรื่องนั้นไว้ใน type system ให้เลย:
enum OrderStatus { PENDING PAID SHIPPED DELIVERED CANCELLED}
type Order { id: ID! status: OrderStatus!}ค่าของ status การันตีว่าเป็นหนึ่งในห้าชื่อนั้น เพราะ server จะปฏิเสธค่าอื่นตั้งแต่ก่อน resolver ทำงาน ค่า enum เขียนเป็น SCREAMING_SNAKE_CASE ตามธรรมเนียม และไม่ใช่ string — PAID คือสัญลักษณ์ที่ schema รู้จัก ไม่ใช่ข้อความ "PAID"
flowchart TD Field["Order.status : OrderStatus"] Enum["enum OrderStatus"] V1["PENDING"] V2["PAID"] V3["SHIPPED"] V4["DELIVERED"] V5["CANCELLED"] Field --> Enum Enum --> V1 Enum --> V2 Enum --> V3 Enum --> V4 Enum --> V5
เมื่อใดควรใช้ enum เทียบกับ string
หัวข้อที่มีชื่อว่า “เมื่อใดควรใช้ enum เทียบกับ string”นี่คือหนึ่งในการตัดสินใจด้านการออกแบบที่คุณจะพบบ่อยที่สุด ใช้ enum เมื่อชุดของค่าที่ถูกต้องนั้น รู้จัก เล็ก และมีความหมายต่อ domain ของคุณ — สถานะ บทบาท ขนาด ทิศทางการเรียงลำดับ ผลตอบแทนเป็นของจริง:
- การตรวจสอบฟรี — ค่าที่ไม่ถูกต้องถูกปฏิเสธโดยระบบ type ไม่ใช่โดย resolver ของคุณ
- สัญญาอธิบายตัวเอง — client เห็นทุกค่าที่ถูกต้องตามกฎใน schema
- เครื่องมือ autocomplete ตัวเลือกให้ และตัวสร้างcodeผลิต enum type ที่แท้จริง
ใช้ String ธรรมดาเมื่อชุดของค่านั้น เปิดกว้างหรือมาจากผู้ใช้ — ชื่อ คำค้นหา บันทึกแบบอิสระ อะไรก็ตามที่คุณควบคุมไม่ได้ กฎคร่าว ๆ ที่ใช้ได้ผลคือ ถ้าคุณเขียนค่าที่ถูกต้องทั้งหมดออกมาได้ตั้งแต่วันนี้ และรายการนั้นแทบไม่เปลี่ยน นั่นคือ enum ถ้าค่ามาจากผู้ใช้หรือโตแบบคาดเดาไม่ได้ นั่นคือ string
enum ในการใช้งานจริง รันได้จริง
หัวข้อที่มีชื่อว่า “enum ในการใช้งานจริง รันได้จริง”ตัวอย่างด้านล่างนิยาม enum OrderStatus กับ type Order แล้ว query คำสั่งซื้อเพื่ออ่านสถานะกลับมาเป็นค่า enum กด Run
field status คืนค่า SHIPPED หาก resolver ผลิตค่าที่อยู่นอก enum — เช่น RETURNED — GraphQL จะยกข้อผิดพลาดขึ้นมาแทนที่จะส่งข้อมูลไม่ดีไปยัง client อย่างเงียบ ๆ นั่นคือการรับประกันที่ enum มอบให้คุณ
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| custom scalar ให้ type safety สำหรับ domain-specific value | custom scalar ต้องเขียน serialization/parsing logic เอง |
| enum ป้องกัน client ส่ง invalid value | enum เพิ่มยาก — เพิ่ม value อาจ break client ที่ exhaustive switch |
| built-in scalar (String, Int, Float, Boolean, ID) ครอบคลุม 90% | enum ลบ value ยากมาก — backward compatibility |
| description บน scalar และ enum เป็น live documentation | String ที่ masquerade เป็น enum เสีย validation |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”ใช้ String แทน Enum อาการ:
status: Stringแทนstatus: OrderStatus- client ส่ง
"PENDING","pending","Pending"ได้ทั้งหมด — ไม่มี validation - ใช้ enum เมื่อมี finite set ของค่า
เพิ่ม Enum Value โดยไม่แจ้ง Client อาการ:
- เพิ่ม
CANCELLEDในOrderStatusenum - client ที่มี exhaustive switch ไม่มี case ใหม่ — runtime error
- communicate breaking change และให้ client update ก่อน
💡 ตัวอย่างจากของจริง
GitHub:
- ใช้ custom scalar
URIแทนStringสำหรับ URLDateTimescalar บังคับ ISO 8601 format — ไม่ต้องเดาว่า format คืออะไรStripe GraphQL API (internal):
- enum สำหรับ currency, payment status, webhook event type
- ทำให้ client code เป็น exhaustive และ type-safe