Gino Kaleb gino kaleb SYS ADMIN
/

A simulated backend in the browser

To keep the frontend moving, I replicated the backend logic in JavaScript and used localStorage as the database. That let me test the whole system without a single server.

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 localStorage as 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, and switch from 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 StaticAPI of 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 real fetch behind it. That detail was key: the day the real backend was ready, I’d just swap StaticAPI for 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.