It all started with a classic plan. I was designing “Project Phoenix”, a system to manage academic requests: a Spring Boot backend handling business logic, security, and data persistence, and a plain JavaScript frontend consuming the API.
On paper it worked. In practice it became a bottleneck.
The problem: waiting for the backend
The core of the system was a fairly complex business rules engine. A request wasn’t simply “approved” or “rejected”: it went through a cascade of validations. Is the request period open? Does the user already have requests in progress? Does their academic status allow it? Any administrative holds? Each reason (schedule conflict, work situation, health issue) triggered a different decision tree, which also depended on the user’s semester, GPA, and other factors.
My frontend work kept getting interrupted. I’d build a form but needed the eligibility endpoint to enable the submit button. I’d design the “my requests” view, but the final data shape was still being discussed on the backend. Every rule change meant waiting for the backend to be updated, deployed, and available. It wasn’t anyone’s fault: that’s how a tightly coupled system behaves in its early stages. But progress was slow.
In the middle of that slowness I asked myself a question: what if, instead of faking API responses, I simulated the whole backend?
Replicating the engine in the browser
The idea went beyond returning static JSON. I decided to port the full business logic (the EligibilityService and RequestEvaluationService that lived in Java) to a JavaScript file, so I’d have a working replica of the decision engine running entirely in the browser.
First I defined a clear API contract and fixed it as the pact between front and back. Then came the replication:
- A database in the browser: I used
localStorageas an ephemeral database. Every request “submitted” in the prototype was stored there, persisting between sessions and simulating a real history. - The rules engine in JavaScript: I translated every
if,else if, andswitchfrom Java into JavaScript functions. The same deterministic logic that decided a request’s fate, now on the client. - A static API: I created a global object, a
StaticAPIof sorts, with methods named exactly like the REST endpoints:submit(),fetch(),cancel(). The rest of the frontend used that object without knowing there was no realfetchbehind it. That detail was key: the day the real backend was ready, I’d just swapStaticAPIfor the HTTP client without touching the interface logic.
The result was a mock that didn’t feel like a mock: a self-contained development environment.
What I gained besides speed
The first thing was the obvious one: development sped up. I could build and test complete user flows with no external dependencies, and polish the interface against instant, coherent responses.
The second surprised me more: user testing got better. Instead of showing static mockups or happy-path prototypes, I handed people a working application. They tried edge cases, explored different scenarios, and their feedback was about the real experience. That surfaced bugs and ambiguities in the business rules themselves long before the final backend version was written.
And the third: real decoupling. The backend could focus on security, scalability, and optimization, knowing the API contract was the only point of contact. The mock worked as living documentation of the expected behavior.
When the time came to integrate the real backend, the process was almost boring: I replaced the static data provider with the HTTP client and everything worked on the first try. No surprises, no last-minute rework. The hard part was already done.