Belajar GraphQL - Project: Enterprise SaaS Application
Episode 43 of 51

Belajar GraphQL - Project: Enterprise SaaS Application

Episode 43 membangun aplikasi multi-tenant SaaS: arsitektur database per tenant versus shared database, tenant context propagation, manajemen organisasi dengan undangan dan role, billing dengan Stripe subscriptions, hingga fitur enterprise seperti SSO dan audit logs.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Project terakhir di fase real-world: aplikasi multi-tenant SaaS — satu aplikasi melayani banyak organisasi pelanggan dengan isolasi data dan kebijakan billing yang aman. Ini pola arsitektur yang dipakai hampir semua perusahaan SaaS modern. Episode 43 membangunnya dari fondasi multi-tenancy sampai fitur enterprise: arsitektur isolasi, propagasi tenant context, manajemen organisasi, billing, dan fitur seperti SSO serta audit logs.

Multi-Tenancy Architecture

Database per Tenant vs Shared Database

  • Database per tenant: isolasi terkuat, mudah membackup per tenant, tapi mahal dan sulit diskalakan dengan ribuan tenant.
  • Shared database dengan tenant_id: hemat biaya dan mudah diskalakan, tapi isolasi bergantung pada disiplin query.
  • Hybrid: cluster shared untuk tenant kecil, database khusus untuk enterprise besar.
JSModel shared dengan tenantId
model Project {
  id       Int    @id @default(autoincrement())
  tenantId Int
  name     String
  @@index([tenantId])
}

Untuk seri ini, kita memakai shared database dengan tenantId pada setiap tabel — pola paling umum untuk SaaS.

Tenant Isolation Strategies

  • Schema-level: pastikan setiap query menyertakan filter tenantId.
  • Resolver-level: derive tenant dari context, bukan dari input client.
  • Database-level: row-level security bila database mendukung.
JSFilter tenant di setiap query
Query: {
  projects: async (_, __, ctx) => {
    return ctx.prisma.project.findMany({
      where: { tenantId: ctx.tenant.id },
    });
  },
},

Paling penting: tenant ditentukan dari context (hasil autentikasi), bukan dari argument yang dikirim client — jika client bisa memilih tenant, isolasi runtuh.

Tenant Context Propagation

JSTenant di context
context: async ({ req }) => {
  const user = await authenticate(req.headers.authorization);
  if (user) {
    const membership = await findMembership(user.id);
    return { user, tenant: membership.tenant, role: membership.role };
  }
  return { user: null, tenant: null, role: null };
},

Setiap resolver membaca ctx.tenant dan ctx.role, misalnya ctx.tenant.id. Ini membuat propagasi tenant otomatis ke seluruh query dan mutation tanpa kode berulang.

Organization Management

Team Structure dan Roles

Organisasi (tenant) punya anggota dengan role — ini memakai pola RBAC dari episode 14:

Schema organisasi
type Organization {
  id: ID!
  name: String!
  members: MemberConnection!
  plans: [Plan!]!
}
 
type Member {
  user: User!
  role: OrgRole!
}
 
enum OrgRole {
  OWNER
  ADMIN
  MEMBER
  VIEWER
}

Invitation System dan Subscription

Undangan anggota memakai pola: invite dibuat dengan email + token, calon anggota menerima link, dan mengklaim keanggotaan saat signup atau join. Hak istimewa dibatasi: hanya OWNER dan ADMIN yang bisa mengundang dan mengubah role — pemeriksaan memakai ctx.role.

Billing dan Subscriptions

Stripe Subscription Integration

Billing memakai Stripe subscriptions, bukan checkout sekali bayar; install dengan npm install stripe:

JSBuat subscription Stripe
import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
 
async function createSubscription(_, args, ctx) {
  const customer = await stripe.customers.create({
    email: ctx.user.email,
    metadata: { tenantId: String(ctx.tenant.id) },
  });
 
  const subscription = await stripe.subscriptions.create({
    customer: customer.id,
    items: [{ price: args.input.priceId }],
    payment_settings: { save_default_payment_method: "on_subscription" },
  });
 
  return { subscriptionId: subscription.id };
}

Usage-Based Billing dan Plan Management

Untuk usage-based billing, kirim metrik pemakaian ke Stripe secara berkala; plan management membandingkan plan.quota dengan pemakaian saat ini dan menampilkan upgrade prompt saat melewati batas. Invoice generation ditangani Stripe; timpa template bila perlu branding khusus.

Enterprise Features

SSO (SAML dan OAuth) dan Audit Logs

  • SSO: dukungan SAML dan OIDC untuk perusahaan — biasanya lewat provider seperti Okta atau Auth0. Setelah user login SSO, tenant di-map berdasarkan domain email.
  • Audit logs: catat semua aksi penting (siapa, apa, kapan, tenant mana) — wajib untuk compliance.
Audit log entry
type AuditLog {
  id: ID!
  actor: User!
  action: String!
  resource: String!
  tenantId: ID!
  createdAt: DateTime!
}

Tulis audit log dari resolver yang menangani aksi sensitif (update role, delete data, perubahan billing).

Rate Limits per Tenant dan White-labeling

  • API rate limits per tenant: setiap tenant punya kuota berbeda sesuai plan (episode 15).
  • White-labeling: konfigurasi logo dan warna per tenant, disimpan sebagai setting.
  • Custom domains: mapping domain ke tenant untuk akses langsung.
JSQuota per tenant
const quota = await ctx.redis.get(`quota:${ctx.tenant.id}`);
if (Number(quota) >= ctx.tenant.plan.limit) {
  throw new Error("Kuota bulanan habis");
}

Penutup

Inti yang harus dibawa pulang:

  • Multi-tenancy memakai database per tenant, shared database, atau hybrid.
  • Tenant diturunkan dari context, bukan dari input client — kunci isolasi.
  • Organisasi dan role memakai pola RBAC dengan hierarchy.
  • Stripe subscriptions menangani billing dan usage-based pricing.
  • SSO, audit logs, dan rate limit per tenant adalah fitur enterprise wajib.
  • Setiap query diresap oleh filter tenantId agar isolasi terjaga.

Di episode 44 selanjutnya kalian akan mempelajari API design patterns dan best practices — naming conventions, konsistensi struktur, standar dokumentasi, best practices performa dan keamanan, hingga strategi evolusi schema. Kalian akan merancang API yang production-ready dari nol!

Belajar GraphQL - Project: Enterprise SaaS Application | Belajar GraphQL