Prisma + PostgreSQL for SaaS in 2026: the reasonable default
Single schema, type safety, migrations, Prisma Studio. Why Prisma + Postgres is the lowest-friction DB stack for indie SaaS.
Israel Palma
3 min read
"PostgreSQL + Prisma" has consolidated as the default for indie SaaS in 2026. Not because it's the
only option (Drizzle, Kysely, raw SQL exist), but because the combo of type-safety + migrations + DX
is hard to beat.
This guide explains why this combo wins, what patterns work, and what mistakes to avoid.
## What Prisma + PostgreSQL gives you
- **Single declarative schema**: define models in `schema.prisma` and TS types, client, and
migrations come from it.
- **End-to-end type safety**: TypeScript knows every table's shape, you autocomplete queries.
- **Automatic migrations**: change schema, run `migrate dev`, done.
- **Prisma Studio**: GUI to inspect the DB with no extra install.
PostgreSQL adds:
- Maturity and universal compatibility (every provider supports it)
- Native JSON (`@db.JsonB`) for flexible fields
- Built-in full-text search
- Triggers, views, etc., when you need them
## Basic SaaS schema
```prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
content String
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
@@index([authorId, createdAt])
}
```
Short schema, everything you need to start.
## Single client pattern
Wrong:
```ts
// Every time you import, you instantiate a new PrismaClient
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
```
In dev, hot-reload opens new connections each time. You saturate the DB.
Right:
```ts
// src/lib/db/client.ts
import { PrismaClient } from '@/generated/prisma/client';
declare global {
var prisma: PrismaClient | undefined;
}
export const db = globalThis.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalThis.prisma = db;
}
```
Anywhere: `import { db } from '@/lib/db/client'`.
## Team migrations
```bash
bunx prisma migrate dev --name add-billing-table
```
Creates SQL file, applies it to your local DB, regenerates the client. You commit it with the code.
In production:
```bash
bunx prisma migrate deploy
```
Applies pending migrations. Doesn't generate new SQL, only applies what's already in
`prisma/migrations/`.
## Repeated query pattern
If you run the same query in 3+ places, pull it into a file:
```ts
// src/lib/db/queries/users.ts
import { db } from '@/lib/db/client';
export async function getUserById(id: string) {
return db.user.findUnique({
where: { id },
include: { posts: { orderBy: { createdAt: 'desc' } } },
});
}
```
Gives you:
- Reuse
- One place to change if schema changes
- Simpler tests (mock the helper, not the whole Prisma)
## Prisma 7 and the PostgreSQL adapter
Since Prisma 7 (2025), the client requires an explicit adapter:
```ts
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '@/generated/prisma/client';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const prisma = new PrismaClient({ adapter });
```
Small but critical detail: skip it and standalone scripts (`bun script.ts`) blow up on init.
## Common errors
**1. No indexes on filtered columns**: if you do `where: { userId }`, add an index. Without it, in
production with 100k rows, the query takes seconds.
**2. `findMany` without pagination**: if the table can grow, paginate (`take`, `skip`, or cursor).
Returning 50k rows from an endpoint kills the server.
**3. `findUnique` with non-unique fields**: only works on `@unique` fields. Otherwise use
`findFirst`.
**4. Loading relations you don't use**: `include: { posts: true }` when you don't need them is DB
work you throw away.
## When NOT to use Prisma
- You need very custom SQL (complex CTEs, advanced window functions) — use raw queries for those
cases or move to Kysely
- Backend team that prefers raw SQL — also valid
- Huge datasets (>10TB) where you control every query
For 95% of indie SaaS, Prisma is the reasonable default.
## Bottom line
Prisma + PostgreSQL in 2026 is the lowest-friction DB stack for indie SaaS. One schema, one client,
type-safety, migrations. Everything else is noise.
Start here. When you grow and need something sharper, you'll have data to decide.
Enjoyed this article?
Subscribe for more tutorials and tips on building products with AI