Skip to main content
Back to Blogs
Software EngineeringOctober 27, 20262 min readBy Mansi Mahant

Modeling a CMS on Firestore After Years of Relational Databases

NoSQL doesn’t remove the need for a schema - it just moves the enforcement from the database to your code.

FirestoreDatabase DesignBackend
Cloud Firestore has Gone GA, Lower Pricing Tiers, New Locations, and more!

Most of my backend work has been on relational databases, so building this portfolio's CMS on Firestore meant unlearning a few habits before I could use it well.

No Joins Means No Joins

The first adjustment is obvious but easy to underestimate: there's no query that pulls a project and its related media in one round trip the way a SQL join would. Every piece of content — projects, artwork, blog posts — stores its own media references directly as embedded objects (a URL, a media ID, alt text) rather than pointing at rows in a separate table. Denormalizing like that felt wrong at first, coming from a relational background where duplication is usually a smell. In a document database, it's often just how reads stay fast.

The Schema Still Exists - It's Just in TypeScript

A relational database enforces its schema for you; Firestore enforces nothing. The schema still exists in this project, just one layer up - as TypeScript interfaces and Zod validators that every write passes through before it reaches the database. Every Project, Artwork, and Blog document has a corresponding Zod schema in the validators layer, and the repository layer is the only code allowed to talk to Firestore directly. Nothing writes to a collection without going through that gate first.

Designing for "Which Fields Exist, Not Just Which Are Filled"

The other adjustment was building for optional, toggleable content sections - a project might have an architecture write-up or might not, a case study might show performance metrics or might not. Rather than letting the frontend guess from empty strings, each content type carries its own `sections` map recording which parts are actually meant to be shown. That turned out to be the right NoSQL instinct: store the shape of the content explicitly, since there's no schema migration tool to fall back on later if a field's meaning quietly changes.

Share:XLinkedInWhatsApp