Back to Insights
TechnicalApr 30, 20266 min read

Postgres-First: Why We Reach for the Database Before the Framework

A
Aqib
Founder & Lead Engineer

When a new SaaS project lands on our desk, the first thing we open isn't VS Code. It's a blank Postgres schema. Most product complexity is data complexity in disguise, and the teams that win are the ones that get the data model right before they pick a framework.

The schema is the product

Every screen, every API, every report eventually maps back to rows in tables. If the relationships are wrong, no amount of clever frontend code will save you. We sketch the entire schema on a whiteboard before anyone writes a component — entities, foreign keys, indexes, the whole thing.

It usually takes 2–3 sessions to converge. The arguments that surface during this exercise are exactly the arguments you'd otherwise have in production three months in, except now they cost an afternoon instead of a sprint.

Constraints are documentation that runs

  • NOT NULL on every column that should never be empty — your future self will thank you.
  • CHECK constraints for state machines (status IN ('pending', 'active', 'archived')).
  • Unique partial indexes for soft-delete-aware uniqueness.
  • Foreign keys with ON DELETE behavior chosen explicitly, not by accident.

Use the database, don't fight it

Postgres has window functions, JSONB, full-text search, materialized views, LISTEN/NOTIFY, and row-level security. Most product teams ignore all of this and rebuild it badly in application code.

A two-line SQL query with a window function will out-perform 200 lines of TypeScript every time, and it's far easier to reason about. Learn the database you already pay for.

Migrations are forever

Treat migrations like API contracts. Once shipped, you don't edit them — you write a new one. Use a tool that generates them from your schema (we like Drizzle) so the migration always matches the source of truth.

And run them in CI against a fresh database on every PR. The first time you catch a broken migration before it hits production, the workflow pays for itself.

Frequently asked questions

Why model the database before picking a framework?
Most product complexity is data complexity in disguise. If the schema is wrong, no amount of clever frontend code will save you. Getting the relationships right up front saves weeks of churn later.
What database does Aqib Ops default to?
Postgres. It's mature, fast, has the richest feature set of any open-source database (window functions, JSONB, full-text search, materialized views, LISTEN/NOTIFY, RLS), and is supported by every serious hosting platform.
What constraints do you always add?
NOT NULL on every column that should never be empty, CHECK constraints for state machines, unique partial indexes for soft-delete-aware uniqueness, and explicit ON DELETE behavior on every foreign key.
Should application code reimplement database features?
Almost never. A two-line SQL query with a window function will out-perform 200 lines of TypeScript and is easier to reason about. Use the database you already pay for.
How do you handle migrations?
Treat them like API contracts. Once shipped, you don't edit them — you write a new one. We use Drizzle to generate migrations from the schema, and we run them in CI against a fresh database on every PR.