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

Building with Yoga

นี่คือจุดที่ทุกอย่างมาบรรจบกัน คุณได้ออกแบบ schema เขียน resolver เพิ่ม mutation จัดการ pagination กับ error ทำให้กราฟปลอดภัย และได้เรียนวิธีใส่ type เขียน test และ federate มาแล้ว บทนี้จะประกอบทุกแนวคิดเข้าเป็น TypeScript service เล็ก ๆ ตัวเดียวด้วย GraphQL Yoga — GraphQL server แบบ batteries-included ที่รันได้ทุกที่ที่ Node รันได้ — แล้วส่งโปรเจกต์ที่รันได้จริงให้คุณเอาไปต่อยอด

Yoga server สร้างจากสามชิ้นส่วนแบบเดียวกับที่ GraphQL server ทุกตัวมี เพียงแต่ตอนนี้ผูกเข้าด้วยกันอย่างชัดเจน:

  • typeDefs — schema ในรูป SDL นี่คือ contract ของคุณ เขียนแบบ schema-first (ดูบท Schema-First vs Code-First) ข้างในมี Query สำหรับอ่านและ Mutation สำหรับเขียน
  • resolvers — function ที่ทำให้แต่ละ field มีค่าจริง คุณเจอมาแล้วใน Queries & Resolvers และต่อยอดในโมดูล mutations
  • context — object ประจำ request ที่ Yoga สร้างครั้งเดียวแล้วส่งเข้าไปในทุก resolver เป็น argument ตัวที่สาม ใช้เก็บของที่มีขอบเขตแค่ request นั้น เช่น ผู้ใช้ปัจจุบัน handle ของ database และ request id เป็นที่วางของที่ Security & Performance สนใจได้อย่างสะอาด

Yoga นำสามชิ้นนั้นมาแปลงเป็น HTTP server ที่มี GraphiQL playground ในตัว, การจัดการ error ที่สมเหตุสมผล และรองรับ subscriptions — โดยคุณไม่ต้องเขียน boilerplate เองเลย

flowchart TD
  TypeDefs["typeDefs (SDL contract)"]
  Resolvers["resolvers (fulfil each field)"]
  Schema["Executable schema (createSchema)"]
  Context["createContext (per request)"]
  Yoga["createYoga"]
  Http["HTTP server + GraphiQL"]
  TypeDefs --> Schema
  Resolvers --> Schema
  Schema --> Yoga
  Context --> Yoga
  Yoga --> Http
typeDefs กับ resolvers ประกอบกันเป็น schema แล้ว Yoga เติม context ประจำ request และเสิร์ฟผ่าน HTTP

context คือชิ้นส่วนที่มือใหม่ใช้ไม่เต็มที่ คุณนิยามฟังก์ชัน createContext ที่รันต่อหนึ่ง request — อ่าน header, ค้นหาว่าใครเป็นผู้เรียก — แล้วคืน object ออกมา Yoga ส่ง object นั้นให้ทุก resolver เป็น argument ตัวที่สาม ดังนั้น resolver ใด ๆ ก็อ่านได้ว่าใครเป็นคนถามโดยไม่ต้อง parse request ซ้ำ:

function createContext() {
// Runs once per request; the return value is the resolver context.
return { greetingPrefix: 'Hello' };
}
const resolvers = {
Query: {
// The third argument is the context object above.
hello: (_parent: unknown, args: { name: string }, ctx: { greetingPrefix: string }) =>
`${ctx.greetingPrefix}, ${args.name}!`,
},
};

แบบนี้ resolver จะไม่ต้องยุ่งกับงานท่อประจำ request เลย แค่ขอสิ่งที่ต้องการจาก ctx ส่วนการ wire ก็อยู่ที่เดียว

โปรเจกต์ด้านล่างคือ schema.ts ฉบับเต็ม export ทั้ง typeDefs (schema เล็ก ๆ ที่มีหนึ่ง query และหนึ่ง mutation) และ resolvers (พร้อม store ในหน่วยความจำและคำทักทายที่อ่าน context) กด Open in StackBlitz เพื่อเปิด GraphQL Yoga server จริงใน browser แล้วเปิด GraphiQL playground ตาม URL ที่พิมพ์ออกมาเพื่อลอง operation ต่าง ๆ

Node.js

Needs the Node.js runtime — open in StackBlitz to run.

พอ server รันแล้ว ลองอ่านข้อมูลก่อน:

{ greeting(name: "Graph") messages { id text } }

greeting พิสูจน์ว่า context ไหลเข้ามา — prefix "Hello" มาจาก createContext ไม่ใช่จาก resolver จากนั้นรันการเขียนแล้วอ่านอีกครั้ง:

mutation { postMessage(text: "Built it end to end") { id text } }

Query messages อีกครั้งแล้ว message ใหม่ของคุณก็จะอยู่ตรงนั้น ในไม่กี่บรรทัด คุณได้ออกกำลังให้ query หนึ่งอัน, mutation หนึ่งอัน, request context และ store ในหน่วยความจำ — รูปร่างแบบเดียวกับที่ Yoga service บน production ทุกตัวมี เพียงแต่เล็กกว่า

service ตัวนี้คือคอร์สทั้งคอร์สย่อส่วน typeDefs คือการออกแบบ schema, resolvers คืองานจากโมดูล queries-and-resolvers กับ mutations, context คือจุดที่ security และ performance เข้ามาเกี่ยว เติม codegen กับ test จากบทก่อนหน้าในโมดูลนี้ แล้วคุณจะได้ service ที่มี type และผ่านการ test จากนั้นแยกตาม domain แล้ววาง gateway ไว้ข้างหน้า ก็ได้บท federation ครบ ไฟล์เล็ก ๆ ไฟล์เดียว แต่มีทุกแนวคิด

ข้อดีข้อแลกเปลี่ยน
envelop plugin system — composable middleware สำหรับทุก phaseplugin order สำคัญ — ผิดลำดับทำให้ behavior ผิด
รองรับ Node.js, Deno, Bun, Cloudflare Workersecosystem เล็กกว่า Apollo Server
built-in GraphiQL IDE — development ง่ายต้องเรียนรู้ envelop ถ้าต้องการ custom plugin
lightweight กว่า Apollo Server — startup เร็วกว่าcommunity resource น้อยกว่า Apollo

ใช้ Yoga โดยไม่มี Envelop Plugin สำหรับ Production อาการ:

  • deploy production โดยไม่มี auth plugin, rate limit plugin, logging plugin
  • default Yoga setup เหมาะกับ development ไม่ใช่ production
  • เพิ่ม plugin ที่จำเป็นก่อน production: useAuth, useRateLimit, useOpenTelemetry

GraphiQL เปิดใน Production อาการ:

  • GraphiQL accessible ใน production — introspection ให้ข้อมูล schema ทั้งหมด
  • ปิด GraphiQL และ introspection ใน production หรือ protect ด้วย auth

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

The Guild (Yoga creators):

  • ใช้ Yoga ใน production สำหรับ client ขนาดใหญ่หลายราย
  • plugin ecosystem รวม DataLoader, Rate Limit, Auth, Tracing

Hive (GraphQL Schema Registry):

  • สร้างโดย The Guild — integrate กับ Yoga โดยตรง
  • track schema usage, detect breaking change, monitor performance
สามชิ้นส่วนใดที่ประกอบขึ้นเป็น GraphQL Yoga service ในบทเรียนนี้?
Yoga รัน createContext เมื่อใด และ resolvers รับค่าที่คืนกลับมาได้อย่างไร?
ในเดโม field greeting แสดงให้เห็นอะไร?
ทำไม Yoga service เล็ก ๆ นี้จึงถูกเรียกว่าจักรวาลย่อของคอร์สทั้งหมด?