Business Matching — Kemenperin, Ministry of Industry
Four stack eras since 2024 — the complete development history of Kemenperin’s Business Matching platform, from a PHP 5 monolith to the Svelte + Go + Python system running at bm-online.id today.
- Year
- 2025
- Focus
- Government
- Reading
- 6 min· 13 sections
- Published
- Web

Project Overview
"The program never stopped running through four complete rewrites. Each era kept the matching workflow intact while the technology underneath changed entirely."
The Evolution — 4 Stacks, One Mission
Every rewrite preserved the program’s core workflow while replacing the foundation underneath it.
01. PHP 5 — The First Platform · 2024
The original Business Matching platform: a server-rendered PHP 5 monolith where company registration, product catalogs, and matchmaking requests all flowed through a single codebase backed by one MySQL database.
02. PHP 8 — The Modernized Monolith · 2024 — Early 2025
A ground-up rebuild on PHP 8 through 2024 into early 2025: typed signatures, Composer dependencies, PDO prepared statements throughout, and a proper admin panel for curating participants and scheduling matching sessions.
03. React + TypeScript → Hono — The Decoupled Era · Late 2025
By the end of 2025 the system split in two: a React single-page app for participants and a TypeScript API built on Hono. JWT auth replaced server sessions and PostgreSQL replaced MySQL — a real API for the first time.
04. SvelteKit + TypeScript → Go → Python — bm-online.id · 2026 — Present
The 2026 platform: a SvelteKit app server that owns auth, SSR, and writes — a Go backend mirroring the hot reads and running realtime aggregation — and Python services for embeddings and forecasting.
Complete rewrites across the platform’s lifetime.
Continuous operation since the first PHP 5 release in 2024.
PHP, TypeScript, Svelte, Go, and Python across the eras.
The current Svelte + Go + Python system in production.
Era 01 — PHP 5 — The First Platform
Early PHP 5 interface — February 2024
PHP 5 platform — March 2024
PHP 5 admin — March 2024
Era 02 — PHP 8 — The Modernized Monolith
PHP 8 dashboard — January 2025
Company management
Matching workflow
Event scheduling
Participant list
Reports
Settings
PHP 8 platform — February 2025
Era 03 — React + TypeScript → Hono — The Decoupled Era
React participant app — December 2025
Company profile, decoupled
Matching interface
Directory view
Detail view
React + Hono — December 2025
React dashboard
React + Hono final build
Era 04 — SvelteKit + TypeScript → Go → Python — bm-online.id
System Architecture — Era 05 in Production
Service Constellation
Every service and its role — browsers hit Cloudflare first; Neon is the only writer’s target.
Hot Read Path — Layered Cache
How dashboard and list reads stay fast: L1 memory → Upstash L2 → Go mirror → stale-on-outage.
Realtime Fan-Out
Three realtime surfaces — Phoenix Channels for dashboards, SSE for live values, watermark-driven so Neon can scale to zero.
Service Map
The moving parts behind bm-online.id — each bounded, each replaceable.
SvelteKit :5173 — Primary App Server
Owns auth (Better Auth, HTTP-only cookies), SSR, the public REST API with scoped API keys, and every write via Drizzle. Idle-aware replication polling lets Neon scale to zero; heap-capped at 1GB on Railway.
Go :8080 — Read Mirror & Realtime Core
oapi-codegen-generated OpenAPI mirror of the hot read paths, a 3s watermark poller that aggregates dashboard snapshots, Redis/NATS publisher, built-in WS hub, and a general reverse proxy with LRU cache and circuit breaker.
Phoenix :4000 — Dashboard Channels
Subscribes Redis Pub/Sub with circuit breaker and dead-letter, rebroadcasts dashboard:update over Phoenix Channels. HTTP-poller fallback to Go when Redis is down; token-based WS auth.
Python :5001/:5003 — ML Services
ONNX all-MiniLM-L6-v2 embeddings behind a Rust LRU/TTL cache (:5002) so repeat inference never hits Python; Prophet forecast service with Indonesian holiday regressors and nightly recompute.
Neon Postgres — Source of Truth
pgvector HNSW index stores codification embeddings for in-DB cosine similarity; pg_trgm GIN indexes power KBLI/HS-code/SIRUP search; pg_stat_statements for diagnostics.
Edge & Infra — Cloudflare + Upstash + MinIO
WAF and edge rate limits, R2 CDN serving immutable hashed assets, Upstash Redis as L2 cache and Go↔Phoenix bus, MinIO for uploads, Meilisearch :7700 for full-text search. One shared openapi.yaml contract generates Go stubs and SvelteKit types.
SvelteKit, Go, Phoenix, two Python services, Rust cache, Meilisearch, MinIO, n-eight.
Watermark interval when SSE subscribers exist — 5.5min idle for Neon scale-to-zero.
In-memory L1, Upstash L2, and a 1h stale tier served on Go outage.
Single source — oapi-codegen for Go, openapi-typescript for SvelteKit.
Lessons Carried Forward
What four rewrites of the same government platform teach you.
Rewrite Beats Patch
Each era replaced the foundation rather than patching it. Migrations were planned around program milestones — never mid-event — so the platform never went dark for participants.
Decouple Only When It Pays
The React + Hono split was right for its moment — real mobile usage, multiple API consumers. When the team no longer needed that flexibility, the Svelte + Go era consolidated again.
Type the Contracts
Every era tightened the contract between frontend and backend: PHP 8 types, then TypeScript end-to-end, then shared schemas between Svelte and Go. Each step deleted a class of integration bugs.
Own the Deploy
From FTP uploads on shared hosting to single-binary Go deploys — each era made shipping safer. The current stack deploys in minutes with a rollback story that actually works.