07/11
Case Study
New June 1, 2025 GovernmentKemenperinPHP

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
Business Matching — Kemenperin, Ministry of Industry

Project Overview

Business Matching is Kemenperin’s (Ministry of Industry, Republic of Indonesia) program for connecting Indonesian manufacturers and IKM actors with buyers, distributors, and export channels. The digital platform behind it has been rebuilt across four stack eras since 2024 — each era matching the technology of its moment while preserving the program’s core mission. This case study documents the full arc: from a PHP 5 monolith through PHP 8 modernization, a decoupled React + Hono architecture, and the current Svelte + Go + Python system running at bm-online.id.

"The program never stopped running through four complete rewrites. Each era kept the matching workflow intact while the technology underneath changed entirely."

Engineering Notes Kemenperin Business Matching

The Evolution — 4 Stacks, One Mission

Every rewrite preserved the program’s core workflow while replacing the foundation underneath it.

01

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

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

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

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.

04 Stack Eras

Complete rewrites across the platform’s lifetime.

2+ Years in Service

Continuous operation since the first PHP 5 release in 2024.

05 Languages

PHP, TypeScript, Svelte, Go, and Python across the eras.

bm-online.id Live Platform

The current Svelte + Go + Python system in production.

Era 01 — PHP 5 — The First Platform

The first incarnation of Business Matching for Kemenperin ran on PHP 5 — a classic server-rendered monolith where every page was generated on request. IKM (small & medium industry) actors could register a company, list products in a catalog, and submit matching requests ahead of ministry-run business matching events. It did the job. Manufacturers adopted a digital matching channel for the first time, and ministry staff gained an admin view over participants. But the stack was aging fast: no dependency isolation, legacy mysql_* patterns in places, and an increasingly brittle deployment on shared hosting. Recap — Proved the model — industry would match online — but the runtime had no headroom left.
Business Matching PHP 5 — early interface, February 2024

Early PHP 5 interface — February 2024

Business Matching PHP 5 — platform view, March 2024

PHP 5 platform — March 2024

Business Matching PHP 5 — admin view, March 2024

PHP 5 admin — March 2024

Era 02 — PHP 8 — The Modernized Monolith

The second era rebuilt the platform on PHP 8 — same monolith shape, professional internals, developed through 2024 and running into early 2025. Typed function signatures, Composer-managed dependencies, and PDO prepared statements replaced the legacy patterns. The registration and matching flows gained server-side validation that actually held up under real ministry events. For the first time the codebase had tests covering the critical paths, and ministry staff got a dedicated admin panel to curate participants, approve companies, and schedule one-on-one matching sessions between buyers and producers. Recap — Same shape, professional internals — the modernization that bought the platform time.
Business Matching PHP 8 — dashboard, January 2025

PHP 8 dashboard — January 2025

Business Matching PHP 8 — company management

Company management

Business Matching PHP 8 — matching workflow

Matching workflow

Business Matching PHP 8 — event scheduling

Event scheduling

Business Matching PHP 8 — participant list

Participant list

Business Matching PHP 8 — reports

Reports

Business Matching PHP 8 — settings

Settings

Business Matching PHP 8 — February 2025 view

PHP 8 platform — February 2025

Era 03 — React + TypeScript → Hono — The Decoupled Era

Era three, landing at the end of 2025, split Business Matching in two: a React SPA for participants and a Hono-powered TypeScript API serving the data. Decoupling unlocked a genuinely mobile-friendly experience for factory-floor users and let the API serve more than one consumer. JWT-based auth replaced server sessions, PostgreSQL replaced MySQL, and the matching workflow became an explicit API contract instead of implicit page logic. The trade-off was real though — two codebases, two deploy pipelines, and validation rules duplicated on both sides. Recap — A real API and a real SPA — but double the maintenance for a small team.
Business Matching React — participant home, December 2025

React participant app — December 2025

Business Matching React — company profile

Company profile, decoupled

Business Matching React — matching interface

Matching interface

Business Matching React — directory view

Directory view

Business Matching React — detail view

Detail view

Business Matching React + Hono — December 2025

React + Hono — December 2025

Business Matching React + Hono — dashboard

React dashboard

Business Matching React + Hono — final build

React + Hono final build

Era 04 — SvelteKit + TypeScript → Go → Python — bm-online.id

Era four is the current production system at bm-online.id — a service constellation, not another monolith. SvelteKit is the primary application server: it owns Better Auth sessions, SSR, the public REST API, and every write to Neon Postgres via Drizzle. The Go backend mirrors the hot read paths at higher throughput and runs the realtime aggregation layer — a 3-second watermark poller feeding Phoenix Channels and SSE. Python owns the compute-heavy ML work through two dedicated services: ONNX embeddings behind a Rust LRU/TTL cache for codification similarity, and a Prophet forecast service with Indonesian holiday regressors. Go calls them over typed internal APIs and stays the single public gateway — one binary in front, purpose-built services behind. Recap — Not a rewrite — a constellation. SvelteKit serves, Go mirrors, Python thinks.

System Architecture — Era 05 in Production

The 2026 platform is a service constellation, not a monolith. SvelteKit is the primary application server — it owns auth, SSR, the public REST API, and every write to the database. The Go backend mirrors the hot read paths at higher throughput and runs the realtime aggregation layer. Python owns the compute-heavy ML work through two dedicated services. Everything sits behind Cloudflare (WAF, rate limits, R2 CDN). Neon Postgres is the single source of truth — pgvector for embedding similarity, pg_trgm for code search. The full engineering document lives in the repository at src/lib/contents/kemenperin/business-matching/ARCHITECTURE.md.

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.

09+ Services

SvelteKit, Go, Phoenix, two Python services, Rust cache, Meilisearch, MinIO, n-eight.

3s Realtime Poll

Watermark interval when SSE subscribers exist — 5.5min idle for Neon scale-to-zero.

03 Cache Layers

In-memory L1, Upstash L2, and a 1h stale tier served on Go outage.

openapi.yaml API Contract

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.

Where It Stands Today

bm-online.id is the living result of all four eras — the same Business Matching program Kemenperin started on PHP 5 in 2024, now running on a Svelte + Go + Python stack that actively improves matches. The history above documents what changed, why it changed, and what each version looked like in production.