Case study — 01
TOEFL Prediction System
Digitalising the university's TOEFL prediction exam, from registration to a QR-verified certificate
A computer-based exam platform with three timed sections, automatic scoring, role-based access, offline answer buffering, and verifiable digital certificates.
- Role
- Programmer — design and implementation
- Period
- 2025 — present
- Status
- Live
Overview
The full project title is Pengembangan Website TOEFL Prediction System Untuk Mendukung Digitalisasi Pelaksanaan Ujian TOEFL Prediction di Universitas Ubudiyah Indonesia. The long name describes the goal precisely: move an exam that used to be run on paper into a system that handles the whole cycle.
In practice it is one application with three jobs: deliver a three-section prediction test, score it without hand-counting, and issue a certificate that a third party can verify.
It is used by the language centre, exam operators, and students. The live site is public; the assessment content and internal records are not.
The problem
A TOEFL prediction test is a scheduled, high-volume event. When it runs on paper, every step is manual: participants register on a list, the room is checked in by hand, each section is timed with a clock on the wall, answer sheets are collected and scored, and certificates are typed one by one with a stamp and a signature.
That workflow has three structural weaknesses:
- Scoring is slow and repetitive. Three sections have to be counted and converted into a scale score for every participant.
- Certificates are hard to verify. A paper certificate is trusted because of its stamp, not because anyone can check it against the record.
- The exam is fragile. A lost answer sheet, a mistimed section, or a misread grid is hard to recover from after the fact.
Digitalisation was not about putting a nicer form on the process. It was about making each of those steps deterministic and auditable.
What the system does
The application covers the full exam lifecycle:
- Registration and scheduling. Participants are registered and assigned to an exam session.
- Exam delivery. Three modules run in sequence — Listening, Structure and Written Expression, and Reading — each with its own timer and navigation rules.
- Submission. Answers are stored as they are made and submitted once the section is closed.
- Scoring. Raw answers are converted into a section score and a total scale score.
- Certification. A digital certificate is generated with a QR code that resolves to a verification page.
Operators see sessions and participants. Participants see their schedule and results. Permissions are not cosmetic: an operator should not be able to change a completed score, and a participant should not be able to read another participant’s record.
Architecture
| Layer | Choice | Why |
|---|---|---|
| Application | Next.js + React + TypeScript | One codebase for server-rendered pages and exam interfaces |
| Data | PostgreSQL with Drizzle ORM | Relational data with typed queries and migrations |
| Auth | Better Auth | Session handling without building credential flows from scratch |
| Authorization | CASL abilities | Role rules defined once and checked at the boundary |
| Validation | Zod | One schema for form input, API payloads, and parsing |
| Client state | Zustand | Small, explicit state for the exam runner |
| Offline buffer | IndexedDB via Dexie | Answers survive a dropped connection |
The important detail is not the list of libraries. It is where each one is allowed to own a decision. The database owns integrity, Zod owns input shape, CASL owns permission, and the React layer owns presentation. When those boundaries blur, exam software becomes hard to trust.
Offline behaviour
Exam rooms are not always reliable networks. A browser that loses connectivity halfway through a listening section must not lose answers or let the participant continue into a state the server will reject later.
The sync model is built around three rules:
- Answers are written to IndexedDB first and treated as the local source of truth until acknowledged.
- Sync requests are idempotent, so a retry after a timeout cannot create a duplicate answer.
- The server remains authoritative for timing and submission state, so a client with a slow clock cannot extend a section.
That last rule matters most. Offline support is not “let the client do whatever it wants and reconcile later”. It is a narrow allowance for network failure, with the server still deciding what counts.
Authentication and authorisation
Every route is scoped by role: participant, operator, and administrator. Better Auth handles sessions; CASL expresses what each role may do.
The rule I keep coming back to is that permission checks belong close to the data, not only in the UI. Hiding a button is a usability decision. Rejecting the request is a security decision, and both have to exist.
Scoring and certificates
Scoring is deliberately boring: each section produces a raw score, the raw scores are converted into section scores, and the section scores are combined into the reported scale. Keeping that pipeline explicit means a disputed result can be traced back to an answer, not argued about.
Each certificate carries a QR code pointing at a verification URL. The page resolves the certificate identifier against the database and shows the result. A screenshot of a certificate is not proof; a record in the system is.
Testing
The project was tested from the outside, by role, rather than only through unit tests.
- Participant flow: register, start, answer, submit, read result, download certificate.
- Operator flow: create a session, monitor participants, close a section.
- Boundary cases: submitting after the timer expires, reconnecting mid-section, and attempting to access a session with the wrong role.
That approach fits the risk. The expensive failures in this system are workflow failures, not pure functions with wrong return values.
Challenges and what I learned
Time is a server concern. Any timer that lives only in the browser is a suggestion. The server has to be able to say when a section ended, regardless of what the client believes.
Offline is a design constraint, not a feature toggle. Once answers can be buffered locally, every write path needs an idempotency story.
Authorization is a data problem. Role checks scattered across components drift. Expressing abilities once made the behaviour predictable.
Certificates are a trust product. The QR code is only useful if the verification page is authoritative and the identifier cannot be guessed.
The system is live and in use. What I would improve next is operational visibility: better dashboards for session health and clearer audit trails for score changes.