Building in Public#building-in-public

Migrating 64 Tools From Hardcoded Data to a Real Database

By Mohamed Abdi Guled

When just add it to the array stops scaling, and what the migration actually looked like.

Migrating 64 Tools From Hardcoded Data to a Database

*When "just add it to the array" stops scaling, and what the migration actually looked like.*

Early on, ShabelleHub's tool listings — 64 tools across 14 categories — lived as a hardcoded JavaScript object in data/index.js. That's a completely reasonable way to start a project. It's also a structure that runs out of road fast, and the migration off of it is worth documenting because it's a step almost every growing directory-style site eventually has to take.

Why Hardcoded Data Stops Working

A static array in a source file means every content update — a new tool, a corrected description, a changed pricing tier — requires a code change, a commit, and a redeploy. For a handful of tools, that's fine. At 64 tools and growing, it becomes the bottleneck for basically everything: no non-technical editing, no dynamic filtering that scales cleanly, and every content fix bundled into a deploy cycle meant for code changes, not content changes.

The Migration Itself

The move was to Firestore (in the version of ShabelleHub running on Firebase at the time), and the practical challenge was translating 64 JavaScript objects — each with fields like name, category, description, pricing, and tags — into a proper Firestore collection structure, along with the 14 categories as their own referenced collection rather than free-text strings repeated across every tool.

This is exactly the kind of repetitive, structure-preserving transformation that's well suited to generating as a script rather than doing by hand — writing a migration script that reads the existing data/index.js export and writes each entry into Firestore in the correct shape, rather than manually re-entering 64 records through a UI. The output was packaged as a migration script, ready to run against the live database.

What "Ready" Doesn't Mean

Generating the migration script was the easy part. Whether that migration actually got *run* against production — fully executing the write of all 64 tools and 14 categories into Firestore — is a separate question from whether it was *written correctly*. A generated script sitting unexecuted is progress, but it isn't the same as a completed migration, and it's worth being honest about that distinction rather than assuming "the script exists" means "the data moved."

Why This Matters Beyond This One Project

Any project that starts with hardcoded fixture data — which is a genuinely good way to prototype quickly — eventually faces this same fork: either keep manually editing source code for every content change, or invest in the migration to a real database once the content volume and edit frequency justify it. The signal to actually do the migration isn't a specific tool count; it's the moment content updates start feeling like they're fighting the deploy cycle instead of using it.

Structuring Categories as Their Own Collection

One decision worth calling out specifically: categories weren't migrated as a free-text field repeated on every tool document — a tool having category: "Writing" typed out identically 64 times, with all the risk of typos and inconsistency that implies. Instead, the 14 categories became their own referenced collection, with each tool storing a reference to a category document rather than a duplicated string. This is a small piece of relational thinking imported into a document database that doesn't enforce it by default, and it paid off almost immediately: renaming a category, or adding metadata to it (an icon, a description, a sort order) became a single edit in one place, rather than a find-and-replace across dozens of tool records that a flat, denormalized structure would have required.

What the Script Actually Had to Handle

Beyond the straightforward field-mapping, the migration script needed to handle a few messier realities in the original hardcoded data: a handful of tools had inconsistent field names left over from earlier ad-hoc edits (a desc field on some entries where the rest used description), a few pricing values were stored as strings in one entry and numbers in another, and category names had at least two cases of inconsistent capitalization that would have silently created duplicate category documents if migrated literally rather than normalized first. Catching and normalizing these inconsistencies before writing to Firestore, rather than after, avoided needing a second cleanup pass on the live database.

The Verification Question, Answered Honestly

Following up specifically on whether this migration was actually run against production: the honest status is that the migration script was generated, reviewed for correctness against the source data, and prepared as a ready-to-execute artifact — but confirming its live execution requires checking the actual Firestore console directly for the presence of all 64 tool documents and 14 category documents, rather than assuming completion from the script's existence. That verification step is a deliberate, separate action worth taking before considering this migration closed, precisely because "the script is correct" and "the script has been run" are two different claims that are easy to conflate under time pressure.

Takeaway

Starting with hardcoded data isn't a mistake — it's often the fastest way to get a project moving. But treat the eventual database migration as a real, deliberate project with its own verification step (did the data actually land correctly in production?) rather than something that's "done" the moment the migration script is written.

📬 Get the latest AI tool reviews

Expert picks and comparisons, weekly. No spam.

← Back to Blog