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

Scalars และ Enums

ทุก query ไม่ว่าจะลึกแค่ไหน สุดท้ายก็ไปจบที่ leaf คือค่ารูปธรรมค่าเดียว leaf พวกนั้นคือ scalar กับ enum ที่เป็นชิ้นส่วนเล็กที่สุดของ schema แต่การเลือกให้ดีคือสิ่งที่ทำให้ field อธิบายตัวเองได้และใช้ผิดยาก บทนี้ครอบคลุม scalar แบบ built-in, custom scalar และ enum — เครื่องมือที่คมที่สุดสำหรับชุดตัวเลือกที่ตายตัว

GraphQL มาพร้อม scalar type ห้าแบบ ทุก schema ใช้ได้เลยโดยไม่ต้องประกาศอะไร:

  • Int — จำนวนเต็มแบบมีเครื่องหมายขนาด 32 บิต เหมาะกับการนับ ปริมาณ และจำนวนเต็มน้อย ๆ
  • Float — จำนวนทศนิยมแบบมีเครื่องหมายความละเอียดสองเท่า (double-precision) เหมาะกับราคา คะแนน การวัดค่าต่าง ๆ
  • String — ข้อความ UTF-8
  • Booleantrue หรือ false
  • ID — ตัวระบุที่ไม่ซ้ำ (unique identifier) serialize เป็น string แต่ ทึบเชิงความหมาย (semantically opaque) คือมีไว้ใช้เป็นคีย์ ไม่ใช่เอาไป parse หรือคำนวณ

ความต่างระหว่าง ID กับ String สำคัญ แม้ทั้งคู่จะส่งกันในรูปข้อความ การประกาศ field เป็น ID บอกผู้อ่านทุกคนว่า “นี่คือตัวระบุ ให้ปฏิบัติเหมือน token” ที่เป็นคำสัญญาคนละแบบกับ “นี่คือข้อความที่มนุษย์อ่าน”

type Product {
id: ID!
name: String!
priceUSD: Float!
stock: Int!
inStock: Boolean!
}

ห้าแบบที่ 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 เดิมซ้ำขึ้นมาเอง

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 จำกัด field ให้เป็นหนึ่งในชุดของค่าที่มีชื่อแบบตายตัว

นี่คือหนึ่งในการตัดสินใจด้านการออกแบบที่คุณจะพบบ่อยที่สุด ใช้ enum เมื่อชุดของค่าที่ถูกต้องนั้น รู้จัก เล็ก และมีความหมายต่อ domain ของคุณ — สถานะ บทบาท ขนาด ทิศทางการเรียงลำดับ ผลตอบแทนเป็นของจริง:

  • การตรวจสอบฟรี — ค่าที่ไม่ถูกต้องถูกปฏิเสธโดยระบบ type ไม่ใช่โดย resolver ของคุณ
  • สัญญาอธิบายตัวเอง — client เห็นทุกค่าที่ถูกต้องตามกฎใน schema
  • เครื่องมือ autocomplete ตัวเลือกให้ และตัวสร้างcodeผลิต enum type ที่แท้จริง

ใช้ String ธรรมดาเมื่อชุดของค่านั้น เปิดกว้างหรือมาจากผู้ใช้ — ชื่อ คำค้นหา บันทึกแบบอิสระ อะไรก็ตามที่คุณควบคุมไม่ได้ กฎคร่าว ๆ ที่ใช้ได้ผลคือ ถ้าคุณเขียนค่าที่ถูกต้องทั้งหมดออกมาได้ตั้งแต่วันนี้ และรายการนั้นแทบไม่เปลี่ยน นั่นคือ enum ถ้าค่ามาจากผู้ใช้หรือโตแบบคาดเดาไม่ได้ นั่นคือ string

ตัวอย่างด้านล่างนิยาม enum OrderStatus กับ type Order แล้ว query คำสั่งซื้อเพื่ออ่านสถานะกลับมาเป็นค่า enum กด Run

JavaScript

field status คืนค่า SHIPPED หาก resolver ผลิตค่าที่อยู่นอก enum — เช่น RETURNED — GraphQL จะยกข้อผิดพลาดขึ้นมาแทนที่จะส่งข้อมูลไม่ดีไปยัง client อย่างเงียบ ๆ นั่นคือการรับประกันที่ enum มอบให้คุณ

ข้อดีข้อแลกเปลี่ยน
custom scalar ให้ type safety สำหรับ domain-specific valuecustom scalar ต้องเขียน serialization/parsing logic เอง
enum ป้องกัน client ส่ง invalid valueenum เพิ่มยาก — เพิ่ม value อาจ break client ที่ exhaustive switch
built-in scalar (String, Int, Float, Boolean, ID) ครอบคลุม 90%enum ลบ value ยากมาก — backward compatibility
description บน scalar และ enum เป็น live documentationString ที่ masquerade เป็น enum เสีย validation

ใช้ String แทน Enum อาการ:

  • status: String แทน status: OrderStatus
  • client ส่ง "PENDING", "pending", "Pending" ได้ทั้งหมด — ไม่มี validation
  • ใช้ enum เมื่อมี finite set ของค่า

เพิ่ม Enum Value โดยไม่แจ้ง Client อาการ:

  • เพิ่ม CANCELLED ใน OrderStatus enum
  • client ที่มี exhaustive switch ไม่มี case ใหม่ — runtime error
  • communicate breaking change และให้ client update ก่อน

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

GitHub:

  • ใช้ custom scalar URI แทน String สำหรับ URL
  • DateTime scalar บังคับ ISO 8601 format — ไม่ต้องเดาว่า format คืออะไร

Stripe GraphQL API (internal):

  • enum สำหรับ currency, payment status, webhook event type
  • ทำให้ client code เป็น exhaustive และ type-safe
scalar แบบ built-in ตัวใดมีไว้สำหรับตัวระบุแบบทึบ (opaque) มากกว่าข้อความที่มนุษย์อ่านได้?
ทำไมคุณจึงอาจนิยาม custom scalar อย่าง DateTime?
เมื่อใด enum เป็นตัวเลือกที่ดีกว่า field แบบ String?
เกิดอะไรขึ้นหาก resolver คืนค่าที่ไม่ใช่หนึ่งในสมาชิกที่ประกาศไว้ของ enum?