TypeScript ORM Comparison — Complete Guide
이 글의 핵심
Prisma, Drizzle, TypeORM, and Kysely each take a different approach to database access in TypeScript. This comparison covers type safety, query ergonomics, performance, bundle size, and migration workflows to help you pick the right tool.
The Four Options
| Prisma | Drizzle | TypeORM | Kysely | |
|---|---|---|---|---|
| Type safety | Excellent | Excellent | Good | Excellent |
| Query style | Object API | SQL-like | Active Record | SQL builder |
| Bundle size | Large (~20MB binary) | Tiny (~50KB) | Medium | Tiny (~20KB) |
| Edge/serverless | Limited | Native | No | Yes |
| Migrations | Schema → Migration | Code-first | Decorators | Manual |
| Raw SQL | Supported | Natural | Supported | Core |
| Learning curve | Low | Medium | Medium | Low-Medium |
| Ecosystem | Large | Growing | Mature | Small |
1. Prisma
Prisma uses a schema file to generate types and a query client.
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
}
# Generate client + migrate
npx prisma migrate dev --name add-users
npx prisma generate
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
// Fully typed queries
const users = await db.user.findMany({
where: { email: { contains: '@example.com' } },
include: { posts: { where: { published: true } } },
orderBy: { createdAt: 'desc' },
take: 10,
})
// users: (User & { posts: Post[] })[]
// Create with relations
const user = await db.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: [
{ title: 'First Post', content: 'Hello world', published: true }
]
}
},
include: { posts: true }
})
// Transaction
await db.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email: 'bob@example.com', name: 'Bob' } })
await tx.post.create({ data: { title: 'Welcome', content: '...', authorId: user.id } })
})
Prisma strengths:
- Best-in-class DX — intuitive, auto-complete everywhere
- Schema → types → client in one flow
- Prisma Studio (visual DB browser)
- Strong documentation
Prisma weaknesses:
- Large binary (~20MB Rust query engine)
- Doesn’t work in Cloudflare Workers without Accelerate
- Complex raw SQL integration
- Slower cold starts in serverless
2. Drizzle ORM
Drizzle defines schema in TypeScript — no separate schema file.
// db/schema.ts
import { pgTable, text, boolean, timestamp } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
export const users = pgTable('users', {
id: text('id').primaryKey().default(sql`gen_random_uuid()`),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
export const posts = pgTable('posts', {
id: text('id').primaryKey().default(sql`gen_random_uuid()`),
title: text('title').notNull(),
content: text('content').notNull(),
published: boolean('published').default(false).notNull(),
authorId: text('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}))
import { drizzle } from 'drizzle-orm/node-postgres'
import { eq, like, desc } from 'drizzle-orm'
import * as schema from './schema'
const db = drizzle(pool, { schema })
// SQL-like queries
const users = await db
.select()
.from(schema.users)
.where(like(schema.users.email, '%@example.com'))
.orderBy(desc(schema.users.createdAt))
.limit(10)
// Relational query (auto-joins)
const usersWithPosts = await db.query.users.findMany({
with: {
posts: { where: (posts, { eq }) => eq(posts.published, true) }
},
limit: 10,
})
// Insert
const [user] = await db.insert(schema.users)
.values({ email: 'alice@example.com', name: 'Alice' })
.returning()
// Transaction
await db.transaction(async (tx) => {
const [user] = await tx.insert(schema.users)
.values({ email: 'bob@example.com', name: 'Bob' })
.returning()
await tx.insert(schema.posts)
.values({ title: 'Welcome', content: '...', authorId: user.id })
})
Drizzle strengths:
- Tiny bundle — works in Cloudflare Workers, Deno, Bun
- SQL-like API — easy to understand what query is generated
- Fast — minimal abstraction overhead
- TypeScript-first schema
Drizzle weaknesses:
- Less beginner-friendly than Prisma
- Smaller ecosystem/community
- No GUI (Drizzle Studio is newer)
3. TypeORM
TypeORM uses decorators on classes.
// entity/User.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm'
import { Post } from './Post'
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ unique: true })
email: string
@Column()
name: string
@OneToMany(() => Post, post => post.author)
posts: Post[]
@CreateDateColumn()
createdAt: Date
}
// entity/Post.ts
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn('uuid')
id: string
@Column()
title: string
@Column('text')
content: string
@Column({ default: false })
published: boolean
@ManyToOne(() => User, user => user.posts)
author: User
@Column()
authorId: string
}
import { DataSource } from 'typeorm'
const dataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL,
entities: [User, Post],
synchronize: false, // use migrations in production!
logging: false,
})
await dataSource.initialize()
const userRepo = dataSource.getRepository(User)
// Find with relations
const users = await userRepo.find({
where: { email: Like('%@example.com') },
relations: { posts: true },
order: { createdAt: 'DESC' },
take: 10,
})
// Query builder (more complex queries)
const result = await userRepo.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('post.published = :published', { published: true })
.orderBy('user.createdAt', 'DESC')
.getMany()
TypeORM strengths:
- Mature, battle-tested
- Active Record + Data Mapper patterns
- Works with many databases
TypeORM weaknesses:
- Weaker TypeScript inference than Prisma/Drizzle
- Decorator API can be complex
- Type inference for relations is unreliable
- Not recommended for new projects in 2026
4. Kysely
Kysely is a type-safe SQL query builder — no schema file, SQL-first.
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
// Define database types manually
interface Database {
users: {
id: string
email: string
name: string
created_at: Date
}
posts: {
id: string
title: string
content: string
published: boolean
author_id: string
created_at: Date
}
}
const db = new Kysely<Database>({
dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL }) }),
})
// Fully typed SQL queries
const users = await db
.selectFrom('users')
.select(['id', 'email', 'name'])
.where('email', 'like', '%@example.com')
.orderBy('created_at', 'desc')
.limit(10)
.execute()
// Join
const usersWithPosts = await db
.selectFrom('users')
.innerJoin('posts', 'posts.author_id', 'users.id')
.select([
'users.id',
'users.name',
'posts.title',
'posts.published',
])
.where('posts.published', '=', true)
.execute()
// Insert
const user = await db
.insertInto('users')
.values({ id: randomUUID(), email: 'alice@example.com', name: 'Alice', created_at: new Date() })
.returning(['id', 'email'])
.executeTakeFirstOrThrow()
// Transaction
await db.transaction().execute(async (trx) => {
const user = await trx.insertInto('users')
.values({ /* ... */ })
.returningAll()
.executeTakeFirstOrThrow()
await trx.insertInto('posts')
.values({ author_id: user.id, /* ... */ })
.execute()
})
Kysely strengths:
- Complete SQL control — no magic
- Excellent TypeScript inference
- Tiny bundle — edge/serverless friendly
- SQL is what you get — no surprises
Kysely weaknesses:
- Must define types manually (or use codegen)
- No built-in migration system
- More verbose than Prisma for simple queries
- Less beginner-friendly
Decision Guide
New project, team of developers, CRUD-heavy app?
→ Prisma (best DX, docs, onboarding)
Edge/serverless (Cloudflare Workers, Deno Deploy)?
→ Drizzle (tiny bundle, native edge support)
Need fine-grained SQL control, complex queries?
→ Drizzle or Kysely
Existing TypeORM project?
→ Keep TypeORM (migration cost not worth it unless you have issues)
Library or tool needing zero dependencies?
→ Kysely (SQL-first, typed, tiny)
Performance Comparison
Benchmarks on PostgreSQL with 10K rows, 100 concurrent requests:
| Operation | Prisma | Drizzle | TypeORM | Kysely | Raw SQL |
|---|---|---|---|---|---|
| Simple SELECT | 3.2ms | 1.1ms | 2.8ms | 1.0ms | 0.8ms |
| JOIN query | 5.1ms | 1.8ms | 4.2ms | 1.6ms | 1.2ms |
| INSERT + return | 2.8ms | 1.2ms | 2.5ms | 1.1ms | 0.9ms |
| Cold start | +80ms | +5ms | +40ms | +3ms | — |
Approximate values. Real-world differences depend on query complexity and infrastructure.
Migration Strategy
| Tool | Approach |
|---|---|
| Prisma | Edit schema.prisma → prisma migrate dev |
| Drizzle | Edit schema files → drizzle-kit generate → drizzle-kit migrate |
| TypeORM | Edit entities → typeorm migration:generate |
| Kysely | Write SQL migration files manually |
Key Takeaways
- Prisma: Best DX and docs — choose for most team projects in 2026
- Drizzle: Best for edge runtimes, complex SQL, zero bundle overhead
- TypeORM: Avoid for new projects — type inference is weaker
- Kysely: Best when you want full SQL control with TypeScript safety
- All four support PostgreSQL, MySQL, SQLite — pick the right tool for your constraints, not the most popular one
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Prisma, Drizzle, TypeORM, and Kysely each take a different approach to database access in TypeScript. This comparison covers type safety, query ergonomics, performance, bundle size, and migration workflows to help you pick the right tool.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Drizzle ORM Complete Guide | Schema· Queries
- Prisma Complete Guide | Schema· Queries
- PostgreSQL vs MySQL | Deep Comparison | When to Use Each
Keywords Covered in This Article (Related Search Terms)
This article covers Prisma, Drizzle ORM, TypeORM, Database, TypeScript, Backend.