The database engine is the easiest decision in your stack, and founders keep treating it as the hardest. Use Postgres. The decisions that actually matter are which host, which ORM, and how tenant isolation is enforced, and only the last one is genuinely dangerous to get wrong.
Use Postgres, and here is why that is not lazy
Three arguments, none of them nostalgia. Operational maturity: decades of production behavior mean whatever weird thing your database does at 3am, somebody has already written it up. Ecosystem breadth: every ORM targets it, every host offers it, and full text search, JSON columns, vector search, and row-level security come without a second datastore. Most products that thought they needed a specialized database needed one Postgres extension. And, increasingly decisive, Postgres dominates the training data behind the models writing your queries, so an agent writing SQL draws on an enormous corpus rather than a thin one.
There are narrow cases where something else is right. Heavy analytics over hundreds of millions of rows belongs in a columnar store, though that threshold is further out than founders imagine. Genuine document workloads, schemas that vary per record rather than a settings blob you could put in a JSONB column, argue for Firebase, which also suits a mobile-first app wanting realtime sync more than relational queries. Edge-first reads where multi-region latency dominates are the case for Turso, which puts SQLite replicas near users and accepts the write model that follows. Convex is the outlier: less a database you query than a reactive backend where data, functions, and subscriptions are one system, hard to leave once you commit.
The corollary: choosing a novel database costs more than it did two years ago, because every generated query inherits the model's weaker priors about it. You are accepting worse output from the tool writing most of your data access code.
The hosting decision
Serverless Postgres (Neon and similar)
Neon separates storage from compute, and the consequences worth caring about are branching and scale-to-zero. Branching gives a preview deployment its own copy of production-shaped data in seconds, which changes how comfortable you are letting an agent touch the schema. Scale-to-zero means an idle project costs almost nothing. PlanetScale offers a similar branching and deploy-request workflow.
The cost is connection discipline. Serverless runtimes open connections aggressively and Postgres does not love that, so you use the pooled connection string and learn which endpoint your migration tool needs before a deploy teaches you. Cold start behavior is worth measuring, not assuming.
Platform Postgres (Supabase and similar)
Supabase gives you Postgres plus auth, storage, realtime, and row-level security in one product. The fastest path from empty repository to authenticated CRUD runs through here, and because the authenticated user ID is visible inside the database, isolation policies live next to the data rather than in middleware.
The cost is coupling: adopting the platform tends to mean adopting its auth and client libraries, and unwinding one later means unwinding several. The subtler cost is that row-level security is easy to half-implement, leaving some tables protected, some protected for only some operations, and a service-role key that bypasses all of it. Half-implemented RLS is worse than none, because it produces the confidence of protection without the fact of it. The Neon versus Supabase comparison shows where the two models diverge.
Managed classic (RDS, Fly, Railway and similar)
Predictable, boring, no surprises as traffic grows. A normal Postgres instance with normal connection limits and behavior matching every piece of documentation written in the last fifteen years, which gives you the smallest gap between your development mental model and production reality.
The cost is more operations work and less polish: no instant branching, no zero-config preview databases, more thought about backups and upgrades. Fine for a team with someone who enjoys infrastructure, painful for a solo founder shipping weekly.
The ORM decision
The real fork is schema-first with a generated client, which is Prisma's model, versus a SQL-adjacent typed query builder, which is Drizzle's. Prisma gives you one declarative schema file, generated types, and a query API that hides SQL. Drizzle gives you schema in TypeScript and queries that look like the SQL they compile to.
For an agent-heavy workflow, what matters is neither performance nor API taste. It is that the schema is a single readable file and migrations are checked into the repository, because that file becomes the agent's map of your domain. A model that reads the whole schema at once writes correct joins and reuses existing columns. A schema scattered across a dozen files, or applied by hand in a dashboard, means the agent works blind and invents columns that do not exist.
Both options satisfy that if used properly. The failure mode is mixing: one ORM, one query pattern, one migration tool, no raw SQL escape hatches because a generated query was awkward once. Mixed data access compounds fastest, because each new piece of generated code copies whichever pattern it saw. Write that constraint into your project brief, and if you do not have one, our AGENTS.md templates include the data-layer section in the form that holds.
Multi-tenancy is the real decision
The three models
Shared tables with a tenant column means one set of tables where every row carries an organization ID: simplest to build, cheapest to run, easiest to migrate, easiest to leak from. Schema per tenant gives each customer their own tables inside one database, making isolation structural and migrations a loop over hundreds of schemas. Database per tenant gives the strongest isolation, at the cost of connection management, provisioning, and a migration story that becomes an infrastructure project.
For almost every early SaaS, shared tables win, and it is not close. The other two satisfy requirements you probably do not have: a contractual guarantee of physical separation, per-customer data residency, or a few very large customers with wildly different volumes. Absent those, shared tables plus real enforcement is correct, and one demanding customer can be moved later.
Enforcement, not convention
Here is the point of this entire article. A tenant column that every query is supposed to filter on will eventually not be filtered on. Not because your team is careless, but because a thousand queries get written under deadline pressure by different people at different times, and one will forget. When an agent writes the query the odds get worse, because a missing WHERE clause looks correct in every test that holds one tenant's data.
So do not rely on convention. Push enforcement below the level where the mistake can be made. Row-level security is one way: policies attached to the tables and evaluated by Postgres, so a query missing its tenant filter returns nothing rather than everything. A scoped repository layer is the other: no code path reaches the raw client, and every data function takes a tenant context as its first argument. Both fail closed. A forgotten check should produce an empty result, never another customer's rows.
This is authorization, not authentication. Knowing who is calling is solved the moment your provider hands you a session. Deciding which rows that identity may touch is a data-layer concern, and the SaaS auth decision guide covers where that handoff sits and how the organization model you pick shapes your tenant column.
What agents get wrong in the data layer
The patterns repeat. N+1 queries, where a loop issues one query per iteration, because the generated code is locally reasonable and only pathological in aggregate. Missing indexes on foreign keys the agent just created, since the migration adds the relationship and nothing else and the problem surfaces months later under real volume. Migrations written but never run, so production and development quietly diverge. And the big one: the tenant filter dropped in a new endpoint, because the agent wrote a fresh query instead of copying a scoped one.
The fix is structural rather than instructional. You can write "always filter by organization ID" in your brief, and you should, but instructions are advisory and structure is not. If the only way to query is through a scoped helper, and the raw client is not importable from feature code, the agent cannot skip the scope because skipping it is not expressible. Same for indexes: a CI check that fails when a foreign key has none catches it every time, while a note in a document catches it when someone reads the document. That is the argument in the agent-ready boilerplate checklist.
Schema decisions that are expensive to reverse
Four decisions deserve ten minutes on day one. Primary key type: integers, UUIDs, or sortable IDs, with real consequences for index locality, whether IDs leak your customer count, and whether they are safe in URLs. Soft delete versus hard delete, which decides whether every query needs a deleted_at filter forever, and whether that filter is one more thing an agent can forget. Timestamps and timezone handling, meaning timestamptz in UTC with conversion at the edges, decided once rather than per table. And whether money is stored as integer minor units, the only correct answer and one routinely violated by generated schemas that reach for a float.
Decide these yourself, because every table generated after the first copies whatever the first one did. That mimicry is usually helpful, and it is also how a bad early decision compounds: an agent will replicate your worst choice across forty tables without flagging it, and the fix is then a coordinated migration rather than a five-minute change.
Starter kits encode these calls for you, an underrated reason to use one. The stacks directory shows which database, ORM, and hosting combinations kits ship together, and the best database for Next.js roundup narrows hosting to options with real integration support.
Migrations and the deploy story
Migrations must be checked into the repository, reviewable in a diff, and runnable in CI. That is not process hygiene, it is a safety requirement in an agent workflow, because an agent that edits the schema without generating a migration has created a production incident with a delay fuse: it works locally, the tests pass locally, and the deploy fails on a missing column or succeeds against a database that drifted. Make migration generation part of the same command as schema editing, and fail CI when the schema and the migration history disagree.
This is where branching stops being a convenience and starts changing your workflow. When a preview environment gets its own branch of production-shaped data, you can see the effects of a schema change before merging: run the migration, exercise the app, check the query plans, throw the branch away. Without that, every migration is a bet against production. With it, a bad one costs a deleted branch. If agents touch your schema regularly, weight branching heavily in the hosting decision.
Frequently Asked Questions
Which database should I use for a new SaaS?
Postgres. The interesting question is where it runs. Take Neon for branching, scale-to-zero economics, and per-preview databases, if you will be disciplined about pooled connections. Take Supabase if you want auth, storage, and row-level security bundled in and value speed to first feature over avoiding coupling. Take PlanetScale if schema change safety at scale dominates. Take a managed instance on RDS, Fly, or Railway if you want boring predictability. All four run the same engine, so the decision stays reversible in a way that picking a different database does not.
Prisma or Drizzle in 2026?
Decide by workflow, not benchmark, because the performance difference is irrelevant at the scale where you are asking. Prisma suits teams that want one declarative schema file, generated types, and SQL kept at arm's length. Drizzle suits people who want to see the SQL they generate, and it has a mild edge for agent-written code: the queries look like SQL, so a model draws on a larger corpus of examples and a reviewer can tell what a query does at a glance. Prisma's countervailing advantage is that single schema file. Either works. Mixing them does not.
How should I handle multi-tenancy?
Shared tables with a tenant column, plus enforcement that makes the scope impossible to omit, either through row-level security or a repository layer where the raw client is unreachable from feature code. That covers the overwhelming majority of SaaS products at any realistic scale and keeps migrations to one operation. Per-tenant schemas or databases earn their complexity only when an external requirement forces them: a contractual isolation guarantee, per-customer data residency, or one enormous customer whose volume distorts everything else. You learn those from a specific deal, not speculatively.
Can I let an agent design my schema?
Use it to draft, never to decide. Agents are good at a plausible first pass and at surfacing tables you had not thought about, and bad at judging tradeoffs that are expensive to reverse. Before accepting a generated migration, check that primary key types match your convention, every foreign key has an index, money is stored as integer minor units, and any new table carries the tenant column and its enforcement. Then read the migration SQL, not the schema diff, because dropped columns and altered types are visible there and easy to miss elsewhere.
Do I need Redis or a queue on day one?
Usually not. Postgres handles your caching needs by being fast enough, and a table with a status column plus a worker polling it is a fine queue at low volume with a fraction of the operational surface. Two signals mean yes: a genuinely long-running job like video processing or a large export that cannot finish inside a request, or measured contention traceable to a specific query that caching actually fixes. Adding either preemptively costs you a service to run, a failure mode to understand, and a second source of truth that can disagree with your database, in exchange for a problem you have not demonstrated you have.