How I Rebuilt an Excel-Like Web App After the First Architecture Failed
Building an Excel-like application sounds straightforward until you start treating a spreadsheet like a normal database application. I learned that the hard way. I was building a web application where users could enter
Building an Excel-like application sounds straightforward until you start treating a spreadsheet like a normal database application.
I learned that the hard way.
I was building a web application where users could enter values into cells, organize information into rows and columns, and perform calculations using formulas.
Then I added another requirement: version history.
Users needed to be able to make changes, undo them, redo them, and return to previous versions of their work.
That requirement exposed a much bigger problem with my original architecture.
The First Approach
My first instinct was to model the spreadsheet using traditional relational database structures.
I created structures for things such as:
- rows
- columns
- cells
- formulas
- values
- relationships between spreadsheet elements
On paper, this seemed reasonable.
A spreadsheet contains rows, columns, and cells, so representing those concepts directly in the database felt like a clean design.
As the application grew, however, the dynamic was different.
A spreadsheet is not just a collection of independent records.
Changing one cell may affect another cell. Formulas can depend on coordinates elsewhere in the document. Rows and columns can move. Cells can be added, deleted, copied, recalculated, or restored.
Then version history entered the equation.
Version History Exposed the Architecture Problem
To support undo and redo, I needed to preserve previous states of the spreadsheet.
With the original architecture, a single user action could affect multiple database records.
Changing a formula might require updating a cell record, recalculating dependent values, maintaining relationships, and recording enough information to reconstruct the previous state.
The number of moving pieces started to compound.
I began running into problems such as:
- slow operations
- complicated synchronization
- unreliable undo and redo behavior
- difficult state reconstruction
- data inconsistencies
- changes being lost in edge cases
- increasingly complicated code
Every new feature required touching several parts of the system.
Eventually, maintaining the architecture became harder than adding features to it.
That was the point where I had to reconsider my position.
The problem was not one bad query or one buggy function. The underlying representation of the spreadsheet was making the application unnecessarily difficult to reason about.
I Rebuilt the Data Model Around JSON
Instead of trying to represent every spreadsheet concept as a separate relational entity, I rebuilt the system around a JSON representation of the spreadsheet state.
The spreadsheet could now be represented as a structured document containing its cells, coordinates, values, formulas, and related state.
Conceptually, a simplified version could look like this:
{
"cells": {
"A1": {
"value": 100
},
"A2": {
"value": 50
},
"A3": {
"formula": "=A1+A2",
"value": 150
}
}
}
The real implementation was more involved, but the important change was the model.
Coordinates such as A1, B4, or F17 became natural identifiers inside the spreadsheet state instead of requiring several database relationships just to locate a value.
That simplified a large portion of the application.
Why JSON Fit This Problem Better
Relational databases are extremely useful, but that does not mean every piece of application state needs to be decomposed into relational tables.
In this case, the spreadsheet itself behaved more like a document.
The cells belonged to the same logical unit. Their meaning depended heavily on their position and on the surrounding spreadsheet state.
Representing that state together made several operations easier.
Reading a spreadsheet became simpler.
Saving it became simpler.
Duplicating it became simpler.
Restoring a previous version became much simpler.
The formula engine also had a clearer structure to work with because spreadsheet coordinates could map directly to values and formulas.
Instead of constantly translating between database IDs, row records, column records, and cell records, the application could work with spreadsheet coordinates directly.
Undo and Redo Became a Versioning Problem
The biggest improvement was how I could think about history.
Previously, undoing one user action meant determining which individual database records had changed and how to reverse each of those changes correctly.
With the new architecture, I could preserve versions of the spreadsheet state.
A simplified history might look like:
Version 41
Version 42
Version 43
Version 44
If the user made a change, a new version could be created.
Undo could move back to an earlier state.
Redo could restore the newer state when appropriate.
There were still important engineering questions to solve, including storage growth, version retention, concurrency, and when versions should be created.
But those were now isolated problems.
I no longer had to reconstruct a spreadsheet from a long chain of unrelated database operations.
That changed the equation.
The Lesson Was Not "JSON Is Better Than SQL"
That would be the wrong conclusion.
The lesson was to choose a representation based on the behavior of the data.
Relational tables still made sense for many parts of the application, such as users, permissions, document metadata, ownership, and other structured business data.
The spreadsheet contents had different characteristics.
They behaved like one interconnected document with dynamic coordinates and formulas.
JSON fit that part of the problem better.
Good architecture often comes down to drawing the boundary in the right place.
Sometimes Rebuilding Is Cheaper Than Continuing to Patch
Rewriting software should not be the automatic response whenever a system becomes inconvenient.
Usually, improving existing code is safer.
But there is also a point where continuing to patch the wrong abstraction becomes wasteful.
I reached that point with this application.
Each workaround was making the next feature harder to implement. Fixes were increasing complexity instead of reducing it.
I could have continued to put up with the architecture and keep adding special cases.
Instead, I rebuilt the core model.
That decision gave me a simpler system that was easier to maintain and easier to extend.
What I Would Do Differently Today
If I were designing the system again from the beginning, I would identify the spreadsheet itself as a document much earlier.
I would separate the system into concerns such as:
- relational data for users, permissions, and document metadata
- JSON-based spreadsheet state
- a dedicated formula calculation layer
- explicit document versioning
- controlled version retention
- validation before accepting saved state
I would also design undo and redo as part of the architecture instead of treating them as features to bolt on later.
That is one of the useful things about building real software.
Sometimes the most valuable lesson is not learning how to implement your first design.
It is learning when to abandon it.
About the Developer
I'm Ayman Atif, a software developer focused on business software, web applications, APIs, automation, reporting systems, and tools that replace complicated manual workflows.
A large part of my work involves taking business processes that have grown around spreadsheets or manual operations and turning them into maintainable software.
You can see more of my work and engineering projects on my portfolio:
If you are looking for a freelance developer to build, improve, or fix a business application, you can contact me directly.
Email: [email protected]
WhatsApp: +212 701-971272
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.