database · tutorial · sqlite

Switch Databases With One Line (Zero Rewrites)

SQLite today, Supabase tomorrow: identical schemas, identical verbs, one env var. The parity that makes switching boring. Inside.

Two US Army operators at a desk in front of a large telephone switchboard, 1940s
Two US Army operators at a desk in front of a large telephone switchboard, 1940s · Photo: U.S. Army Signal Corps, public domain, via Wikimedia Commons

The database choice expired before the product did

Picture it. Mongo gets picked in week two because the tutorial used it.

By month four you want Postgres.

Joins.

Sanity.

A hiring pool that knows SQL.

The migration means rewriting every query, invalidating sessions and rolling out carefully. So Mongo stays another year out of pure switching pain.

Every founder has a version.

Supabase to your own Postgres.

SQLite to managed.

Postgres to cheaper.

A database decision made with week-two information calcifies into architecture by month six. Not because it was right. Because leaving costs more than staying wrong.

Keep the decision reversible. Then week-two guesses never become life sentences.

Welded or switchable

Moment Welded queries One switch
Add a field One place Every engine, same names
Switch engines A rewrite A planned migration
Agent writes queries Engine-specific drift A parity rule enforces sameness
The scale trigger fires Panic and a rewrite A rehearsed flip
Week-two regrets Permanent An errand

Staying too long teaches three lessons.

Queries encode the engine. Raw Mongo aggregations don't translate. Policy-shaped logic doesn't port. SQLite-isms leak into app code within months.

Migrations are feared, so they are delayed. The rewrite quote grows with every feature, which delays the switch, which grows the quote. Only reversibility by design breaks the cycle.

Agents entrench first choices. Each session deepens the current engine's idioms. A parity rule turns agent output into portable output automatically.

What to do

Fix field names identically on all three engines.

A user is the same record everywhere: an ID, a unique email, a paid-access flag and a plan type. Same names, same types, same constraints. Decided once per entity, reviewed in minutes.

A new field goes on all three engines or the change is incomplete.

Speak through the shared actions. Never drivers.

The app asks for "create this user" or "list this person's orders" and never imports a database driver. A SQLite file read and a Supabase network call look identical to callers.

The day of the switch touches no feature files.

Change the setting, move the rows, keep the sessions.

Set the database choice to the new engine and deploy. Rows move by export and import, with counts compared for users, orders and content.

Sessions are signed tokens, so a switch does not by itself end them.

Rehearse the flip on staging so the real one is routine, not ceremony.

The switch drill

Check Welded stack One-switch stack
Switch cost A rewrite of the queries A migration with a checklist
Feature files touched Dozens None expected
Forced re-logins Possible Not required
Agent drift between engines Constant Blocked by the parity rule

The cost

Parity triples the schema and adapter code. Three small pieces per entity. It also forbids engine-native superpowers, like raw aggregations and exotic types.

At solo scale, portable and plain beats welded and excellent.

Databases become deployment details. Not destinies.

Know this first

Parity triples the schema and adapter code. It rules out engine-native tricks.

Portable beats clever at solo scale.

Why BoringPush is the way to do this

Your database is a setting.

SQLite, MongoDB or Supabase. The app code does not change.

Start cheap. Move when you've earned it.

FAQ

Is this realistic for complex queries such as joins and aggregations?

At solo-scale complexity, yes. Joins map across engines when designed portably from the start.

Exotic analytics can use an engine-specific escape hatch, used rarely and documented clearly. Design the common 95% to be portable and mark the exceptions.

How do changes to the database stay portable?

They are additive only: new columns with defaults and new tables if they do not exist. They are applied engine by engine, then verified by comparing row counts. Destructive changes need a deliberate multi-step plan on every engine.

What about Supabase Auth, Storage and policies? Isn't that lock-in?

Only if you make them load-bearing. BoringPush keeps identity in its own user rows, serves files from your own storage addresses and treats policies as extra armor behind app-level guards. Consume managed features through portable seams, and the switch stays bounded.

Should new projects start on SQLite even if they plan on Postgres?

Nearly always. SQLite is the fastest place to develop, with zero setup and file backups.

Parity makes "later" an errand instead of a project. Starting managed and simplifying later rarely happens, because complexity ratchets one way.

How do I convince a team invested in one engine?

Show, don't preach. Stage a flip on staging and demonstrate it. Engineers believe working migrations, not architecture essays.

When is welding to one engine actually correct?

When no switch is plausible: Supabase-everything shops, SQLite-forever solos, or compliance-pinned Postgres. The test: will I run this engine in three years, guaranteed?

If yes, weld proudly. If maybe, keep parity.

Keep reading

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading