When I first started using Prisma, I loved how easy it made database work - migrations, schema changes, Prisma Studio, all just worked.
But once I started deploying more apps on Vercel and serverless environments, I noticed something: Prisma Client felt a bit heavy.
Every time I deployed, my bundle size grew. And I didn’t really need the full ORM - I just wanted a way to manage my schema and write type-safe SQL queries.
That’s when I found a good balance: 👉 Use Prisma only for schema management, and Kysely for queries.
This setup has been super clean for me in my Next.js projects - and in this post, I’ll show you exactly how to do it.
Why Prisma + Kysely Combo
I like to think of it like this:
- Prisma is my database architect. It manages the schema, m…
When I first started using Prisma, I loved how easy it made database work - migrations, schema changes, Prisma Studio, all just worked.
But once I started deploying more apps on Vercel and serverless environments, I noticed something: Prisma Client felt a bit heavy.
Every time I deployed, my bundle size grew. And I didn’t really need the full ORM - I just wanted a way to manage my schema and write type-safe SQL queries.
That’s when I found a good balance: 👉 Use Prisma only for schema management, and Kysely for queries.
This setup has been super clean for me in my Next.js projects - and in this post, I’ll show you exactly how to do it.
Why Prisma + Kysely Combo
I like to think of it like this:
- Prisma is my database architect. It manages the schema, migrations, and gives me tools like prisma studio.
- Kysely is my query builder. It lets me write SQL in TypeScript with full type safety and a smaller runtime size.
- The prisma-kysely generator connects both worlds - it generates Kysely types directly from your Prisma schema.
So, Prisma stays for the dev experience.
Kysely takes over at runtime.
No Prisma Client in production - just lean, type-safe SQL.
Step 1: Install Dependencies
Let’s start by adding the right packages.
I’m using pnpm here, but you can also use npm or yarn. I just prefer pnpm because it’s faster and saves disk space.
pnpm add kysely kysely-neon @neondatabase/serverless
pnpm add -D prisma prisma-kysely
Notice that prisma is a dev dependency, because we’ll use it only for schema and code generation - not at runtime.
Step 2: Write Your Prisma Schema
Here’s a small and simple schema with User and Post models.
// db/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator kysely {
provider = "prisma-kysely"
output = "./types"
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
authorId Int
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
}
Now run the following commands to sync the schema and generate Kysely types:
pnpm prisma generate
pnpm prisma db push
This will create a file like db/types/kysely.d.ts which includes all your types.
Add helpful scripts to your package.json
It’s a good idea to add a few Prisma scripts for convenience:
{
"scripts": {
"db:push": "prisma db push",
"db:generate": "prisma generate",
"db:studio": "prisma studio"
}
}
Now whenever you change something in the Prisma schema, just run:
# To generate the types
pnpm db:generate
# To push to the remote DB
pnpm db:push
This will update both the database and the Kysely types automatically
Step 3: Choose a Kysely Dialect
Kysely doesn’t connect to your database directly - it uses a dialect.
You can think of it as the “bridge” that knows how to talk to your database.
Some dialects are official (like PostgreSQL, MySQL, SQLite), and others are community-built (like Neon, PlanetScale, Turso, etc.).
Let’s see two popular options 👇
🟢 Option 1: Neon (Serverless) Setup
If you’re deploying to Vercel or another serverless platform, Neon is great.
It uses an HTTP-based connection, so you don’t need to maintain a persistent database connection.
// db/index.ts
import { Kysely } from 'kysely'
import { NeonDialect } from 'kysely-neon'
import { neon } from '@neondatabase/serverless'
import type { DB } from './types/kysely'
const db = new Kysely<DB>({
dialect: new NeonDialect({
neon: neon(process.env.DATABASE_URL!),
}),
})
export default db
However, keep this in mind:
kysely-neon runs over HTTP, which means no transaction queries or session-based operations. It’s perfect for simple CRUD or read-heavy apps, but not for complex transactions.
Also note:
If you use WebSockets with Neon, you don’t need kysely-neon. You can just use Neon’s Pool with Kysely’s built-in Postgres dialect.
🟣 Option 2: Postgres (pg) Setup
If you’re running a traditional Node.js or edge-compatible backend, the official pg driver is still the best.
It supports full transactions, connection pooling, and prepared statements.
// db/index.ts
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import type { DB } from './types/kysely'
const db = new Kysely<DB>({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
}),
})
export default db
| Dialect | Connection Type | Supports Transactions | Best For |
|---|---|---|---|
| kysely-neon | HTTP (Serverless) | ❌ No | Vercel, Netlify, Cloudflare |
| pg | TCP (Persistent) | ✅ Yes | Node.js servers or edge backends |
So, in short:
Use kysely-neon for serverless setups, and pg when you need full database features like transactions.
Step 4: Use Kysely in a Next.js Server Component
Let’s try a small example - fetching all users in a server component.
// app/users/page.tsx
import db from '@/db'
export default async function UsersPage() {
const users = await db
.selectFrom('user')
.select(['id', 'name', 'email'])
.orderBy('id')
.execute()
return (
<div>
<h1>Users</h1>
<ul>
{users.map(u => (
<li key={u.id}>{u.name} ({u.email})</li>
))}
</ul>
</div>
)
}
This query is completely type-safe.
If you type the wrong table or column name, TypeScript will catch it immediately - because Kysely’s types come directly from your Prisma schema.
Step 5: Typical Workflow Recap
- Update models in db/schema.prisma.
- Run pnpm db:generate and pnpm db:push to generate the types and sync changes.
- Use the db instance in your Next.js server components, API routes, or actions.
- Validate inputs with Zod or any schema library before inserting.
Example Folder Structure
Here’s how the full setup looks in a clean folder tree:
📦 my-next-app
├── 📁 app
│ └── 📁 users
│ └── page.tsx
├── 📁 db
│ ├── schema.prisma
│ ├── index.ts
│ └── 📁 types
│ └── kysely.d.ts
├── 📁 node_modules
├── .env
├── package.json
├── pnpm-lock.yaml
└── tsconfig.json
Final Thoughts
This setup has worked really well for me in my Next.js apps.
I still get all of Prisma’s nice tools - schema, migrations, Prisma Studio - but without pulling Prisma Client into production.
Kysely makes my queries simple, type-safe, and fast.
And Prisma keeps my database organized.
If you’re using Postgres and want to keep your app lean, type-safe, and serverless-friendly, try this approach once.
It’s clean, easy to maintain, and feels great to work with.
✍️ Quick summary
- Prisma = schema & migrations
- Kysely = type-safe SQL
- Together = clean, fast, serverless-ready setup