Skip to content
ARAfsar RakhaSoftware Engineer
JKT --:--:--HomeAboutProjectsContactGet in touch
© 2026 Afsar Rakha· built in Jakarta, not Gotham
UsesGitHubLinkedInEmailResume
← All projects
02engineering2025

CBT Pustaka Mulia

Live demo ↗

CBT Pustaka Mulia is a production-grade Computer-Based Test platform built for PT Pustaka Mulia Sejahtera to serve national TKA (Tes Kemampuan Akademik) exam simulations at scale. The system is engineered to handle 5,000+ concurrent users per session and 10,000–15,000 users per day, with zero tolerance for answer loss or race conditions.

The problem

A national TKA exam simulation puts thousands of students on the same test at the same minute. A dropped connection, a refresh, or a scoring backlog must never cost a single answer, and the platform has to serve schools, teachers, students and parents with different powers.

5,000+

concurrent users per session

10–15k

users per day

30–60 s

heartbeat auto-save

6

RBAC tiers

Architecture

CLIENTAPIWORKERS AND DATAheartbeat 30–60 satomic DECRenqueue on submitscoresNext.jsstudent, teacher, admin appsExpressTurborepo monorepoBullMQ workersscoring pipelinePostgreSQLanswers, scores, RBACRedistoken quotas, queue

Client

  • Next.js · student, teacher, admin apps

API

  • Express · Turborepo monorepo

Workers and data

  • BullMQ workers · scoring pipeline
  • PostgreSQL · answers, scores, RBAC
  • Redis · token quotas, queue

Flows

  • Next.js → Express · heartbeat 30–60 s
  • Express → Redis · atomic DECR
  • Express → BullMQ workers · enqueue on submit
  • BullMQ workers → PostgreSQL · scores
  • Express → PostgreSQL

The hard parts

01

Scoring must never block submission

Submitting an exam only records the answers and enqueues a scoring job on BullMQ. Workers score asynchronously, so a surge of submissions at the bell scales horizontally on the worker side while the submit endpoint stays fast and simple.

02

Race conditions on exam tokens

Session tokens are a finite quota per school. Checking a count and then decrementing it is a race under load, so the quota lives in Redis and is consumed with an atomic DECR; a negative result means denied, with no window for two students to take the last seat.

03

Zero answer loss

The client heartbeats the current answer state every 30 to 60 seconds and on every change of question. A network drop or a refresh resumes from the last heartbeat instead of a blank page, which is the guarantee the schools were buying.

Key contributions

  • Architected the full system on a Turborepo monorepo with Next.js frontend + Express.js backend
  • Implemented async BullMQ scoring pipeline decoupling submission from scoring for horizontal scalability
  • Built atomic Redis DECR token quotas preventing race conditions under concurrent load
  • Designed heartbeat auto-save (30–60s intervals) guaranteeing zero answer loss on network failure or refresh
  • Designed a 6-tier RBAC system: Admin, Operator Sekolah, Guru, Siswa, Kepala Sekolah, Orang Tua
  • Built Admin CMS with real-time monitoring dashboard, token lifecycle management, and question bank management
roleFull-Stack Engineer
year2025
typeengineering

Stack

Next.jsExpress.jsPostgreSQLRedisBullMQTurborepo

Links

Live demo↗

← previous

Klari.id

next →

Asys Academy