Marcom Services: API Bun + Elysia untuk Dashboard Marcom
Backend dashboard Marcom — Elysia di atas runtime Bun, MySQL, JWT, Socket.IO di port terpisah, dan cron kedaluwarsa yang dipanggil lewat HTTP.
Ini backend yang dipakai dashboard Marcom. Satu proses Bun yang menyajikan REST API dan, di port berbeda, server Socket.IO.
Kenapa Bun + Elysia
Yang dicari waktu itu: TypeScript tanpa langkah build, startup cepat di container kecil, dan router yang tidak butuh belasan paket pendukung. Elysia di atas Bun memberi ketiganya — bun run --watch src/index.ts untuk dev, bun src/index.ts untuk produksi, tidak ada tsc di antaranya.
Susunannya rata, satu controller per entitas:
const app = new Elysia()
.use(corsMiddleware)
.use(errorHandler)
.use(authController)
.use(brandController)
.use(clusterController)
.use(fiturController)
.use(jenisController)
.use(materiController)
.use(usersController)
.use(fileRoutes)
.use(cronController);
Di bawah controller ada lapisan services/ yang memegang SQL dan models/ untuk bentuk datanya. Query ditulis langsung dengan mysql2 — tidak ada ORM. Untuk skema yang hanya lima tabel inti dengan empat foreign key, ORM cuma menambah lapisan yang harus dihafal.
Dua port, satu proses
HTTP API di PORT (default 5001); Socket.IO dinyalakan di atas createServer() sendiri di SOCKET_IO (default 5002). Memisahkan port bikin proxy di depannya jauh lebih gampang diatur: yang satu butuh upgrade WebSocket, yang satu tidak.
Rate limit yang terpaksa dimatikan
Di src/index.ts ada blok yang sengaja dikomentari:
// Global rate limit: temporarily disabled to fix body consumption issue
// .use(rateLimit({ duration: 60_000, max: 1000, ... }))
Middleware rate-limit-nya membaca request body untuk keperluannya sendiri, dan setelah itu handler di belakangnya menerima body yang sudah habis dibaca — request POST jadi gagal dengan pesan validasi yang menyesatkan. Butuh waktu lama untuk sadar bahwa yang salah bukan validasinya. Sampai sekarang blok itu tetap dikomentari, bukan dihapus, supaya alasannya tidak hilang.
Bagian yang lain
- Auth — JWT, dengan
authMiddlewaredanrolesMiddlewareterpisah supaya “siapa kamu” dan “boleh apa” tidak bercampur di satu tempat. - Cron —
cronControllermenjaga endpoint yang menjalankan pemeriksaan materi kedaluwarsa, dilindungicronAuth(token sendiri, bukan JWT pengguna). Penjadwalnya di luar; servicenya tetap satu binary. - Email —
nodemailerlewatemailService, dipakainotificationServiceuntuk mengirim daftar materi yang akan habis. - Upload —
fileRoutesmenulis keUPLOAD_DIR(default./uploads), foldernya dibuat sendiri saat boot. - XSS —
elysia-xssdipasang global; input dari form materi memang bisa berisi teks bebas. - Seed —
seeder.tsuntuk data contoh,seeder-prod.tsterpisah untuk data awal yang benar-benar dipakai. Memisahkan keduanya lebih baik daripada satu skrip dengan flag--productionyang suatu hari akan dijalankan tanpa flag.