The TypeScript ORM ecosystem has exploded in three directions: Prisma (schema-driven, full abstraction, slow), Drizzle (SQL-first, runtime-minimal, fast), and Kysely (query builder, zero magic, type-safe). Your choice matters. If you're shipping a Netlify Edge Function, Drizzle boots in 50ms; Prisma takes 200ms+, eating your cold-start budget. If you're building a data-heavy internal API, Kysely's raw SQL flexibility beats Prisma's query limitations. If you're hiring juniors and need a "what does this do" story, Prisma wins — it hides the database under a DSL. But hiding costs. Prisma's bundle footprint is 3× Drizzle's. Prisma's migrations are locked into a generated schema. Drizzle lets you author migrations as SQL (version-controllable, refactorable). If you're shipping to edge, use Drizzle. If you're shipping to a 2GB Node server and never scaling, Prisma is fine. If you hate magic and love raw SQL but want types, use Kysely. This is the decision tree.
What Each Tool Is (and Isn't)
Prisma
What it does: Full ORM. You define a schema in a custom DSL (schema.prisma), Prisma generates migrations, a type-safe client, and database introspection. You query through a fluent API that abstracts SQL. Philosophy: "Database should feel like JavaScript". Relations are loaded eagerly or lazily via chained methods. Sweet spot: Small to mid-size apps (1–50 tables), teams new to ORMs, when you value "I don't need to know SQL". Pain points: Slow cold start, large bundle, N+1 query problems if you're not careful, migrations are generated (you can't hand-edit them safely), queries get complex for non-trivial joins.
Drizzle
What it does: Lightweight ORM / SQL query builder hybrid. You write schema in TypeScript (or SQL), Drizzle generates type-safe query methods. You write queries as SQL with TypeScript bindings. Migrations are plain SQL files you version-control. Philosophy: "SQL is good, we'll just add types and ergonomics". Sweet spot: Edge functions, Serverless backends, SQL-first teams, when cold start and bundle size matter. 50+ table schemas where raw SQL flexibility is essential. Pain points: You write more SQL than Prisma (or Kysely). Ecosystem is newer; fewer integrations. Less "hand-holding" for junior developers.
Kysely
What it does: Query builder, not an ORM. You manage migrations separately (via tools like node-pg-migrate or raw SQL), but Kysely gives you a type-safe query API that's 95% raw SQL but catches type errors at compile time. Philosophy: "SQL is the right abstraction, we'll just make it type-safe". Sweet spot: Teams comfortable writing SQL, when you need full control over query shape, complex analytics queries, or when your schema is driven by the database (introspection). Pain points: You own migrations. Less abstraction = more SQL to write. No automatic relation loading (you JOIN manually, but that's often better for performance anyway).
Side-by-Side: Bundle Size, Cold Start, DX, Migrations
Bundle Size
Drizzle: ~50–80KB (minified + gzipped). Includes the runtime query builder and type generation. Prisma: ~200–350KB (engine is bundled, even if you don't use every feature). Additional @prisma/client dependencies push this higher. Kysely: ~40–60KB. Lightweight query builder, no runtime overhead. Winner: Kysely (smallest), Drizzle (lean), Prisma (heaviest). For serverless, sub-100KB is ideal; Prisma blows that budget alone.
Cold Start (Netlify Edge Function example)
Drizzle: ~30–50ms to initialize. Load SQL definitions, parse schema, hydrate query builder. Prisma: ~200–300ms. Engine initialization, introspection, type loading. Kysely: ~20–40ms. Query builder is lazy-loaded. Winner: Kysely (fastest), Drizzle (acceptable), Prisma (risky for edge functions with tight timeout budgets).
Developer Experience (DX)
Prisma: Highest abstraction. Fluent API, autocomplete-friendly, queries read like JavaScript. A junior developer can build CRUD without touching SQL. Downsides: complex queries require raw SQL fallback; unclear how queries translate to actual SQL; N+1 traps if you're not aware of eager/lazy loading. Drizzle: Middle ground. Schema is TypeScript, queries are SQL with types. You learn the query builder syntax (very SQL-shaped). Autocomplete works, but you're writing SQL. For juniors: expect 2–3 days to ramp. Upside: queries are explicit; no N+1 surprises; migrations are readable. Kysely: SQL-first. If you love SQL, this is joy. Types wrap around raw SQL, so complex queries are straightforward. If you hate SQL, this is pain. Winner: Prisma (for junior onboarding), Drizzle (balance), Kysely (for SQL experts).
Migrations
Prisma: Schema-driven. You edit schema.prisma, run prisma migrate dev, and Prisma generates a migration file (.sql). You can hand-edit the migration, but Prisma owns the generation. Downside: once generated, the migration is immutable; if you mess it up mid-dev, you reset the dev database. For production, Prisma can't roll back destructive migrations safely. Drizzle: SQL-first. You write .sql migration files by hand (or use Drizzle's migration generator as a helper). You version-control them. Drizzle runs them in order. Upside: full control, reviewable diffs, reversible. Downside: you write SQL; Drizzle doesn't auto-generate from schema changes. Kysely: Migrations are your responsibility. Kysely helps introspect but doesn't manage migration files. Use kysely-migration-cli or similar. Winner: Drizzle (best balance — generated or hand-written, fully versioned), Kysely (full control if you like SQL), Prisma (easy for small schema, fragile for complex changes).
Real Patterns: CRUD, Joins, Transactions
Basic Create + Read (Prisma)
const user = await prisma.user.create({
data: { email: 'aiden@aidxn.com', name: 'Aiden' },
});
const user = await prisma.user.findUnique({
where: { id: user.id },
include: { posts: true }, // lazy-load posts
});
Clean. Obvious. No SQL. If you hire someone who's never seen SQL, they'll understand this in 30 seconds.
Basic Create + Read (Drizzle)
import { users, posts } from './schema';
const user = await db
.insert(users)
.values({ email: 'aiden@aidxn.com', name: 'Aiden' })
.returning();
const userWithPosts = await db
.select()
.from(users)
.leftJoin(posts, eq(users.id, posts.userId))
.where(eq(users.id, user.id));
More SQL-shaped (explicit JOIN). If you know SQL, this feels natural. You see exactly what query is running.
Basic Create + Read (Kysely)
const user = await db
.insertInto('users')
.values({ email: 'aiden@aidxn.com', name: 'Aiden' })
.returningAll()
.executeTakeFirstOrThrow();
const userWithPosts = await db
.selectFrom('users')
.leftJoin('posts', 'users.id', '=', 'posts.userId')
.selectAll()
.where('users.id', '=', userId)
.execute();
Query builder wrapping SQL. Types are checked, but you write table/column names as strings (not objects). Lighter than Drizzle's schema-based approach.
Complex Join (All Three Feel Pain)
Prisma: If you need a complex JOIN (e.g., aggregate 3 tables, group, count, order), Prisma's fluent API breaks down. You fall back to raw() / $queryRaw() and lose type safety. The abstraction leaks. Drizzle & Kysely: Write SQL or chained query methods. Both stay in their comfort zone. Drizzle's schema-based approach is more explicit; Kysely's is lighter. Both are faster and more flexible than Prisma's fallback.
Transactions (Prisma)
const result = await prisma.$transaction([
prisma.user.create({ data: { ... } }),
prisma.post.create({ data: { ... } }),
]);
// All-or-nothing: both succeed or both roll back.
Transactions (Drizzle)
await db.transaction(async (tx) => {
const user = await tx.insert(users).values({ ... }).returning();
const post = await tx.insert(posts).values({ ... }).returning();
});
// If anything throws, entire transaction rolls back.
Transactions (Kysely)
await db.transaction().execute(async (trx) => {
const user = await trx.insertInto('users').values({ ... }).returningAll().executeTakeFirstOrThrow();
const post = await trx.insertInto('posts').values({ ... }).returningAll().executeTakeFirstOrThrow();
});
// Same pattern, slightly more verbose.
All three handle transactions. Drizzle and Kysely give you the transaction object inline; Prisma wraps an array of operations. All are safe.
Migration Tooling & Velocity X Usage
Velocity X (Aidxn's full-stack template) uses **Drizzle when not using Supabase client directly** for this reason: edge functions demand sub-50ms initialization, schema is TypeScript (type-safe), migrations are SQL (versionable), and the DX is fast enough for prototype-to-production. Velocity X doesn't use Prisma because cold-start hits the serverless budget. Kysely is an option for Velocity X pro (SQL-heavy analytics templates), but Drizzle covers 95% of use cases.
If you're building with Supabase, skip the ORM entirely for basic CRUD — use supabase-js client directly (it's 50KB and has sub-20ms init). Use Drizzle or Kysely only if you need complex query ergonomics (lots of JOINs, aggregations, type safety on queries).
Edge Runtime Support
Drizzle: Supported on Netlify Edge Functions, Cloudflare Workers, Deno. Works with any database client that supports edge runtimes (e.g., @vercel/postgres, @databases/pg, better-sqlite3). Prisma: NOT recommended for edge functions. Prisma's engine initialization is too slow. Prisma does have experimental edge support (with data proxy), but it adds latency and cost. Kysely: Supported. Lightweight enough for edge. Verdict: If you're shipping to the edge, use Drizzle or Kysely. Skip Prisma.
Six FAQs
Is Prisma "bad"?
No. Prisma is great for monolithic Node servers where cold start doesn't matter and you prioritize team velocity. Use Prisma if: you're building a traditional Express/Fastify API on a 2GB container, you have junior developers, you value "SQL abstraction", and you don't care about 200ms startup overhead. Prisma is bad for serverless, edge, or when bundle size matters.
Should I use Kysely for everything?
If you love writing SQL, yes. If you're hiring juniors who don't know SQL, you'll spend weeks teaching it. Kysely is best for experienced SQL teams building data-heavy applications. For a typical CRUD app, Drizzle strikes a better balance.
Can I switch from Prisma to Drizzle later?
Yes, but it takes effort. You'll need to rewrite queries (Prisma's fluent API → Drizzle's SQL builder), hand-write migrations for anything Prisma auto-generated, and update your schema definitions. If you're starting a new project and uncertain, start with Drizzle. If you're already deep in Prisma and it works, no need to migrate.
Does Drizzle work with every database?
Drizzle supports PostgreSQL, MySQL, SQLite, and is expanding. Prisma supports the same (plus a few others like MongoDB, MariaDB). Kysely is database-agnostic (you provide the client). Pick the ORM that matches your database choice, not the other way around.
What about performance — will Drizzle be faster at runtime?
Not significantly. All three compile to the same SQL under the hood. The performance difference is in startup time and bundle size, not query execution. If your bottleneck is database latency (which it almost always is), the ORM choice doesn't matter. If your bottleneck is cold-start (serverless), Drizzle or Kysely wins.
Do I even need an ORM?
For simple CRUD, no. Use the database client directly (e.g., supabase-js, pg, mysql2). ORMs add value when you have: lots of JOINs (type-safe relation loading), complex migrations (version control), or onboarding juniors (abstraction). If your schema is 3 tables and your queries are straightforward, skip the ORM and use raw SQL with a type generator like kysely-codegen.
The Bottom Line
Pick your ORM based on your constraint, not hype. **Serverless or edge?** Use Drizzle or Kysely. **Traditional server with juniors?** Prisma is fine. **Complex SQL, type-safe, full control?** Kysely. **Balance of speed and developer experience?** Drizzle. Most Aidxn projects use Drizzle because it wins on cold start (edge functions ship faster), migrations (SQL is version-controllable), and it's not slow on traditional servers either. Prisma's abstraction is valuable for some teams, but the hidden cost (bundle size, initialization time) is why we moved away. Kysely is the choice for data engineers who think in SQL and want their database choice to be transparent.
Building a data-heavy product and need guidance on schema design or query optimization? Check out our technical architecture services, or read our latest piece on monorepo architecture to keep your backend scalable as you grow.