Menyatukan semua layanan ke dalam api-gateway (BFF) dengan routing, validasi JWT, rate-limit, CORS, dan agregasi respons, lalu membangun web-app React yang hanya berbicara ke gateway — dan membuktikannya dengan satu alur end-to-end yang utuh

Tujuh layanan sudah bangun di episode 4-9, tapi masing-masing masih jadi pulau. Saatnya menjembatani mereka: api-gateway (dengan peran BFF) akan menjadi satu pintu masuk untuk seluruh client, dan web-app React menjadi wajah pertama yang hanya mengenal gateway — bukan layanan internal.
Mengapa episode ini disebut studi kasus utuh? Karena inilah pertama kalinya satu alur bisnis lengkap mengalir melewati semua layanan dalam satu request-chain yang nyata: dari browser sampai email keluar. Kalian akan merasakan langsung perbedaan dari monolith: log menyebar, cache bekerja, event mengalir — dan gateway jadi titik yang mengatur semuanya.
Satu pintu masuk, URL yang bersih untuk client:
/api/auth/* → auth-service :3001
/api/products/* → product-service :3002
/api/cart/* → cart-service :3003
/api/orders/* → order-service :3004
/api/payments/* → payment-service :3005Client tidak pernah tahu bahwa ada enam layanan berbeda di belakangnya. Ini menyembunyikan kompleksitas internal dan memberi kebebasan menata ulang arsitektur tanpa mengubah API publik.
Gateway menempelkan empat lapisan sebelum meneruskan request:
https://tokokita.id) — bukan wildcard.x-request-id agar semua service bisa melacak satu request di log (fase 5, episode 20).import { Elysia } from 'elysia'
const app = new Elysia()
.derive(({ headers }) => {
return { requestId: headers['x-request-id'] ?? randomUUID() }
})
.onBeforeHandle(async ({ path, headers, set }) => {
if (isPublicRoute(path)) return
const token = headers.authorization?.replace('Bearer ', '')
if (!token) {
set.status = 401
return { error: 'unauthenticated' }
}
const payload = await verifyAccessToken(token)
if (!payload) {
set.status = 401
return { error: 'invalid token' }
}
return { user: payload }
})
// lalu forward via fetch ke service masing-masing (service discovery ringan)Di episode 11 kita akan membangun service discovery dan pattern forward yang benar; di episode 17, ingress akan menggantikan sebagian peran ini di Kubernetes.
Beberapa halaman butuh data dari banyak layanan sekaligus. Contoh: halaman Detail Produk yang menampilkan produk + rating. Rating ada di layanan lain? Bisa dengan agregasi di gateway:
async function productDetail(id: string) {
const [product, rating] = await Promise.all([
fetchInternal(`http://product-service:3002/products/${id}`),
fetchInternal(`http://rating-service:3006/products/${id}/rating`),
])
return { ...product, rating }
}Promise.all menjalankan panggilan paralel sehingga total watu halaman memakai maksimum dari keduanya, bukan penjumlahan.
Caution
Jangan jadikan gateway ratu tunggal yang mengeksekusi seluruh logika bisnis — itu baru monolith kedua. Gateway hanya berbicara presentasi: routing, auth boundary, agregasi ringan. Logika bisnis (validasi stok, kalkulasi total, state machine) tetap di layanan pemilik domain. Saat agregasi mulai berat & spesifik per client, pecahlah menjadi BFF per client (bff-web, bff-mobile).
Frontend dibangun dengan React + Vite di folder apps/web-app. Halaman yang dibutuhkan:
GET /api/products.GET /api/orders/me.Prinsipnya: web-app hanya memanggil api-gateway. Tidak ada fetch langsung ke product-service:3002 dari browser. Semua auth header ditaruh oleh client helper:
const API = '/api'
export async function api<T>(path: string, init: RequestInit = {}) {
const res = await fetch(`${API}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
...init.headers,
},
})
return (await res.json()) as T
}Langkah verifikasi manual di browser:
localhost:8025).payment.succeeded.PAID.Pastikan seluruh stack jalan di Compose:
docker compose -f infra/compose.yaml ps --format '{{.Name}} {{.Status}}'
curl -s localhost:4000/api/healthGateway mengekspos /api/health yang melakukan ping ke tiap service di belakangnya dan melaporkan status aggregat.
Episode 10 menyatukan tokokita:
Promise.all untuk paralel; gateway tidak boleh jadi monolith kedua.Di episode 11 selanjutnya, kita akan masuk ke komunikasi sinkron: REST & gRPC — menghindari sinkron-chain berantai, memutuskan kapan REST vs gRPC, otentikasi service-to-service, serta retry, timeout, dan circuit breaker untuk downstream. Sampai jumpa di episode 11!