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

Node.js SDK

ใช้ @growthbook/growthbook package เดียวกันที่รันใน browser ได้เลยใน Node.js ไม่ต้องติดตั้ง server SDK แยกต่างหาก ความแตกต่างหลักอยู่ที่ lifecycle model: บน server การประเมิน flag เกิดขึ้นภายใน request handlers แทนที่จะเป็นครั้งเดียวตอน app startup

ใน browser app มักสร้าง GrowthBook instance เดียวตลอดอายุของ page แต่บน server แต่ละ HTTP request อาจมีผู้ใช้ต่างกันโดยสิ้นเชิง จึงต้องใช้ per-request scoping — สร้าง (หรือ clone) instance ใหม่ต่อทุก request และ set user-specific attributes บน instance นั้น

บน server แต่ละ HTTP request อาจมีผู้ใช้ต่างกัน จึงต้องสร้าง GrowthBook instance (หรือ clone) ต่อ request และ set user-specific attributes บน instance นั้น แทนที่จะแชร์ instance เดียวตลอดอายุของ process

การดึง feature definitions จาก GrowthBook ทุก request จะเพิ่ม latency และสิ้นเปลือง bandwidth แทนที่ด้วยการดึง payload ครั้งเดียวตอน startup เก็บไว้ใน memory แล้ว reuse: gb.init({ payload: cachedPayload }) พร้อม cached payload วิธีนี้ข้ามการ network call ไปได้เลยโดยยังคง flag evaluation ที่ถูกต้อง

ดึง payload จาก loader instance ด้วย gb.getPayload() แล้วส่งให้ instance ใหม่ผ่าน gb.setPayload(payload) หรือส่งตรงให้ init

ถ้าแชร์ GrowthBook instance เดียวกันที่มี mutable attributes ข้าม requests และเรียก gb.setAttributes(...) ในแต่ละ handler, concurrent requests จะเขียนทับ user context ของกัน — ทำให้การประเมิน flag ผิดพลาดและหาบั๊กได้ยาก การสร้าง instance ใหม่ต่อ request กำจัด race condition นี้ได้อย่างสมบูรณ์

import { GrowthBook, setPolyfills } from '@growthbook/growthbook';

// --- App startup: fetch and cache the payload once ---
let cachedPayload = null;

const loader = new GrowthBook({
  apiHost: 'https://cdn.growthbook.io',
  clientKey: 'sdk-abc123',
});
await loader.init({ timeout: 3000 });
cachedPayload = loader.getPayload();

// --- Per-request handler ---
async function handleRequest(req, res) {
  // Fresh instance per request — no shared mutable state
  const gb = new GrowthBook({
    apiHost: 'https://cdn.growthbook.io',
    clientKey: 'sdk-abc123',
  });

  // Reuse the cached payload — no extra network call
  await gb.init({ payload: cachedPayload });

  // Scope attributes to this request's user
  gb.setAttributes({
    id: req.user.id,
    country: req.headers['cf-ipcountry'] ?? 'US',
  });

  const useBetaUI = gb.isOn('beta-ui');
  res.json({ useBetaUI });

  gb.destroy(); // free listeners
}
ตัวเลือกBenefitCost
Server-side SDKปลอดภัยกว่า ควบคุม logic backend ได้เต็มที่ต้องจัดการ caching/refresh เองเพื่อไม่ query ทุก request
เรียก API ตรงทุก requestง่ายต่อการเข้าใจlatency สูงและ load เพิ่มต่อ backend service ของ GrowthBook
  • re-fetch feature payload ใหม่ทุก request แทนที่จะดึงครั้งเดียวแล้ว reuse — การสร้าง instance ใหม่ต่อ request นั้นเบาและเป็นแนวทางที่แนะนำ ตัวที่สิ้นเปลืองคือ network fetch ของ payload ต่างหาก
  • ไม่ cache feature payload ทำให้ทุก request ยิง network call ไปที่ CDN
  • ลืม refresh payload เมื่อ flag เปลี่ยนใน UI

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

backend service ขนาดใหญ่มัก fetch feature payload ครั้งเดียวต่อ process แล้ว background refresh เก็บไว้ใน memory จากนั้นสร้าง instance ใหม่แบบเบา ๆ ต่อ request โดย reuse payload ที่ cache ไว้ — ได้ทั้ง targeting ที่แยกต่อ request และ latency ที่ใกล้ศูนย์เพราะไม่ต้อง re-fetch

ทำไมต้องสร้าง GrowthBook instance ใหม่ต่อ request บน server?
`gb.getPayload()` คืนค่าอะไร?
ทำอย่างไรถึง reuse cached payload โดยไม่มี network call ใหม่?
ควรเรียกอะไรหลังใช้งาน server-side GrowthBook instance เสร็จ?