- Published on
TypeScript ORM·쿼리 빌더 2026 — Drizzle·Kysely·Prisma·postgres.js 심층 비교 (SQL에 얼마나 가까워야 하는가)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
프롤로그 — "Prisma 또는 raw SQL"의 시대는 끝났다
2026년에도 신규 TypeScript 프로젝트의 첫 번째 회의에서 빠지지 않는 질문이 있다.
"DB 접근, 뭐 쓸 거예요?"
2022년만 해도 답은 보통 두 개였다. Prisma(가장 유명, 풀 ORM) 아니면 raw pg/mysql2 (직접 SQL, 타입은 알아서). 사이에는 TypeORM·Sequelize 같은 클래스 기반 ORM이 끼어 있었지만, TypeScript 친화도와 마이그레이션 경험 때문에 신규 프로젝트에서는 점점 안 쓰였다.
2026년은 다르다. 두 개의 새로운 강자가 자리를 굳혔다.
- Drizzle ORM — "SQL에 가장 가까운" 헤드리스 ORM. 스키마를 TS 코드로 선언하고, 쿼리는 SQL 한 줄 한 줄에 1:1 대응되게 짠다. 번들 크기와 엣지 런타임에 강하다. 2026년 5월 기준 GitHub 스타 31k+, 주간 npm 다운로드 2M+.
- Kysely — "스키마 없는 순수 쿼리 빌더". 어떤 DB 클라이언트(
pg·mysql2·better-sqlite3)도 받아서 타입 안전한 쿼리 빌더로 감싼다. 마이그레이션·스키마 관리는 너의 일이다. GitHub 스타 12k+.
여기에 Prisma가 새로 등장한 두 트럼프 카드.
- Prisma 엔진 재작성 — Prisma 6 이후 기존 Rust 기반 query engine이 TypeScript 네이티브 + Go 엔진 조합으로 단계적으로 교체됐다. 차가운 시작 지연이 줄고, 엣지 런타임 호환성이 정상화됐고, 번들 크기가 의미 있게 줄었다.
- Prisma Postgres — Prisma 사가 직접 운영하는 매니지드 Postgres. unikernel·Bare metal 위의 분리 컴퓨트·스토리지 아키텍처로, free tier가 의외로 후하고 cold start가 빠르다.
그리고 모두를 흔드는 한 갈래.
- postgres.js·pg·mysql2 직접 쓰기 — tagged template literal과 TypeScript의 satisfies·
as const만 잘 쓰면, ORM 없이도 충분히 타입 안전하다는 흐름. "쿼리 빌더 자체가 추상화 과잉" 진영.
이 글은 2026년 5월 기준으로 이 도구들을 직접 비교한다. 추상화 정도, 마이그레이션, 엣지, 번들, escape hatch, 멀티 DB의 6축으로. 그리고 같은 쿼리를 네 가지 도구로 나란히 짜본다.
1장 · 풍경 — 도구 지도
먼저 도구를 분류한다. 모든 게 같은 자리에 있지 않다.
| 분류 | 도구 | 한 줄 요약 |
|---|---|---|
| 풀 ORM (Active Record·Data Mapper) | Prisma·MikroORM·TypeORM·Sequelize | 모델·관계·페치·캐시까지 챙긴다 |
| 헤드리스 ORM | Drizzle | 스키마는 코드, 쿼리는 SQL 1:1 |
| 쿼리 빌더 | Kysely·Knex | 스키마 없이 타입 안전 SQL 합성 |
| Raw 클라이언트 + 타입 헬퍼 | postgres.js·pg·mysql2 + Zod | tagged template + 런타임 검증 |
| 관계형 DSL | EdgeQL(EdgeDB)·SurrealQL | DB가 자체 쿼리 언어 |
이 글의 초점은 굵게 표시한 4그룹의 대표 — Prisma·Drizzle·Kysely·postgres.js — 다. MikroORM·TypeORM은 마지막 장에서 짧게 다룬다.
왜 이 4개인가
- Prisma — 가장 유명하고, 신규 풀스택 프로젝트의 기본값에 가깝다. 2026년의 새 엔진과 Postgres 서비스를 합치면 다시 1군이다.
- Drizzle — 2024~2026 사이 가장 빠르게 자란 도구. Vercel 공식 가이드, Hono·Cloudflare Workers 권장 스택, T3 stack 기본 선택지 중 하나.
- Kysely — Prisma에서 도망 온 팀이 가장 먼저 만나는 도구. 마이그레이션 후기에 자주 등장한다.
- postgres.js — Porsager가 만든 빠르고 작은 Postgres 클라이언트. Bun·Cloudflare Workers와 궁합이 좋다. ORM 없는 진영의 사실상 표준.
2장 · 추상화 스펙트럼 — 어디까지 감출 것인가
데이터 접근 라이브러리는 "DB와 코드 사이에 얼마나 두꺼운 층을 둘지"의 선택이다. 왼쪽이 SQL에 가깝고, 오른쪽이 객체에 가깝다.
SQL 객체
| |
postgres.js -- Kysely -- Drizzle -- Prisma -- MikroORM/TypeORM
(raw) (빌더) (헤드리스 ORM) (풀 ORM) (Active Record)
추상화가 두꺼울수록 좋은가? 단언컨대 아니다. 트레이드오프다.
- 얇은 쪽 장점: SQL 그대로의 표현력, 성능 튜닝의 직진성, 학습/디버그 코스트가 결국 SQL 한 가지로 수렴.
- 두꺼운 쪽 장점: 작성 속도, 도메인 객체와 직접 매핑, 관계·N+1 자동 처리, IDE 자동완성의 풍성함.
- 얇은 쪽 단점: 관계 페치를 직접 짜야 함, 도메인 객체 매핑은 별도 작업.
- 두꺼운 쪽 단점: 추상화가 부족할 때(window function·CTE·복잡한 join) escape hatch가 어색하고, 번들·콜드 스타트·런타임 호환성 모두 비용.
2026년의 분위기는 "SQL에 가깝게, 단 타입은 안전하게" 쪽으로 기울었다. Drizzle·Kysely·postgres.js의 성장이 그 증거다. 다만 Prisma 6의 엔진 재작성으로 두꺼운 쪽도 가벼워져서, "ORM이라서 무거워요" 논리는 옛말이 되어 가고 있다.
3장 · Drizzle — "TypeScript로 쓰는 SQL"
Drizzle은 2026년 신규 프로젝트에서 가장 많이 선택되는 데이터베이스 도구 중 하나다. 핵심 철학 3가지.
- 스키마는 TS 코드 —
schema.ts에서 테이블·컬럼·관계를 선언한다. DB가 진실의 원천이 아니라, TS 파일이 진실의 원천이다. - 쿼리는 SQL 1:1 —
db.select().from(users).where(eq(users.email, '...'))같은 식. SQL을 아는 사람이 보면 무엇으로 컴파일될지 즉시 알 수 있다. - 헤드리스 + 작음 — 런타임 의존성이 거의 없다. Cloudflare Workers·Vercel Edge·Deno Deploy에 그대로 올라간다. 번들 크기는 압축 기준 7~15KB 수준.
Drizzle 스키마
// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
authorId: integer('author_id').notNull().references(() => users.id),
title: text('title').notNull(),
body: text('body').notNull(),
})
Drizzle Kit으로 마이그레이션
# drizzle.config.ts 작성 후
npx drizzle-kit generate # 스키마 변경에서 SQL 마이그레이션 생성
npx drizzle-kit migrate # 실제 DB에 적용
npx drizzle-kit studio # 로컬 GUI
Drizzle의 강점
- SQL escape hatch가 자연스러움 —
sql템플릿 태그로 임의 SQL을 끼워 넣고, 그 부분만 타입을 직접 정의할 수 있다. - 엣지 호환성 — 모든 어댑터(
drizzle-orm/postgres-js,drizzle-orm/neon-http,drizzle-orm/d1)가 엣지 런타임에서 동작. - 여러 DB 지원 — PostgreSQL·MySQL·SQLite·D1·LibSQL·Neon·Planetscale·Vercel Postgres·Bun SQLite. 변환 비용이 거의 없다.
Drizzle의 약점
- 관계 쿼리(Drizzle Relations API)는 발전 중 — 관계로 nested 쿼리를 짤 때 Prisma만큼 매끄럽진 않다. 다만 2025년 v2 출시로 거의 따라잡았다.
- 마이그레이션 충돌 처리는 수동 — 협업 팀에서 마이그레이션 파일 충돌 시 손으로 머지해야 한다.
- 러닝 커브 — SQL을 잘 모르면 Prisma보다 친절하지 않다.
4장 · Kysely — "스키마 없는 순수 쿼리 빌더"
Kysely는 다른 길이다. 스키마를 강제하지 않는다. 너의 DB 스키마는 너의 일이고(SQL 파일·Atlas·Sqitch·Liquibase 무엇이든), Kysely는 그 위에 타입 안전 쿼리 빌더만 제공한다.
// db/types.ts — 너의 DB 스키마와 매칭되는 TS 타입
import type { ColumnType, Generated } from 'kysely'
export interface Database {
user: UserTable
post: PostTable
}
export interface UserTable {
id: Generated<number>
email: string
name: string
created_at: ColumnType<Date, string | undefined, never>
}
export interface PostTable {
id: Generated<number>
author_id: number
title: string
body: string
}
Kysely의 핵심 트릭은 위 Database 인터페이스를 제네릭으로 받아서, 모든 쿼리에서 컬럼 이름·타입을 자동 추론하는 것이다.
kysely-codegen으로 타입 자동 생성
스키마 인터페이스를 손으로 쓰지 않으려면 kysely-codegen을 쓴다. 라이브 DB를 인트로스펙션해서 위 같은 TS 타입을 생성한다.
npx kysely-codegen --url postgres://user:pw@localhost/db --out-file db/types.ts
Kysely의 강점
- SQL 그대로의 표현력 — window function·CTE·
json_agg·UNION ALL등 SQL 거의 전부가 1:1 표현 가능. - DB·마이그레이션 분리 — 마이그레이션 도구는 따로 골라 쓴다. Atlas·Sqitch·dbmate·Flyway 무엇이든.
- 소형 번들 — 핵심 빌더만 가져오면 압축 약 14KB.
- 런타임 의존성 최소 — Cloudflare Workers·Bun·Deno에서 무난.
Kysely의 약점
- 스키마·타입의 진실 일치는 너의 일 — 마이그레이션 후
kysely-codegen을 실행하지 않으면 타입이 거짓말을 한다. CI에 묶거나, 마이그레이션 파일 후크에 자동화 필요. - 관계 페치는 명시적 — N+1·
JOIN·json_agg패턴을 직접 짠다. Prisma·Drizzle 만큼 짧지 않다. - 에코시스템 작음 — 플러그인·튜토리얼·서드파티 어댑터가 다른 두 도구보다 적다.
5장 · Prisma — 새 엔진과 Prisma Postgres
Prisma는 2024~2025 사이 두 번의 큰 변화가 있었다.
5.1 엔진 재작성 — Rust에서 TS 네이티브 + Go로
Prisma 5 시대의 query engine은 Rust로 작성된 별도 프로세스(또는 WASM)였다. 콜드 스타트가 느리고, 엣지 런타임 호환성이 어색했다. Prisma 6 이후 단계적 재작성으로 다음이 바뀌었다.
- TypeScript 네이티브 클라이언트 — 쿼리 컴파일러 다수가 TS로 옮겨졌다. 클라이언트 코드 안에서 직접 SQL을 만든다.
- Go 기반 lightweight 엔진 — Prisma Accelerate·Pulse 같은 매니지드 서비스 측에서 동작.
- 번들 크기 감소 — 클라이언트 사이즈가 의미 있게 줄었다. 정확한 수치는 setup 별로 다르니 Prisma 자체 벤치마크와 릴리즈 노트를 참고하라.
- 엣지 호환성 정상화 — Vercel Edge·Cloudflare Workers에서 직접 동작하는 어댑터가 안정화됐다.
5.2 TypedSQL — Prisma 안에서 raw SQL을 타입 안전하게
Prisma의 약점은 항상 "복잡한 SQL을 못 짠다"였다. TypedSQL은 그 약점을 직접 친다.
-- prisma/sql/findActiveUsers.sql
SELECT id, email, name
FROM "User"
WHERE last_seen_at > $1
ORDER BY last_seen_at DESC
LIMIT $2;
import { PrismaClient } from '@prisma/client'
import { findActiveUsers } from '@prisma/client/sql'
const prisma = new PrismaClient()
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
const users = await prisma.$queryRawTyped(findActiveUsers(since, 50))
// ^ users: Array of typed rows, inferred from the SQL parameters
.sql 파일이 빌드 타임에 검사되고, 파라미터·결과 타입이 자동 생성된다. SQL을 SQL로 쓰되, 타입 안전성은 유지.
5.3 Prisma Postgres — 매니지드 서비스
Prisma 사가 직접 운영하는 Postgres. 핵심 차별화는 3개.
- 분리된 컴퓨트·스토리지 — Neon과 비슷한 아키텍처지만 unikernel 기반.
- 콜드 스타트가 빠르다 — 서버리스 워크로드 친화적.
- Prisma 도구체인과 직결 —
prisma init한 번에 DB 생성·스키마 푸시·클라이언트 생성까지.
매니지드 Postgres 시장은 이미 Neon·Supabase·PlanetScale Postgres·Vercel Postgres가 있어서 경쟁이 치열하지만, Prisma 사용자에게는 가장 친화적인 선택지가 됐다.
Prisma의 강점 (2026)
- 압도적으로 친절한 학습 곡선·문서·튜토리얼.
- 관계·페치·트랜잭션 API가 가장 매끄러움.
- TypedSQL로 escape hatch가 정상화됨.
- 엣지·번들 약점이 크게 줄었음.
Prisma의 약점 (2026)
- 여전히 추상화 층이 가장 두꺼움 — 디버그할 때 SQL이 한 번 떨어졌다 도는 모델이 머릿속에 있어야 함.
- 마이그레이션 도구(
prisma migrate)는 강하지만, migrate dev의 자동 셰도우 DB 생성이 일부 호스팅 환경(권한 부족)에서 거슬리는 경우가 있음. - 라이선스·운영 비용 — Prisma Accelerate·Pulse 같은 매니지드 서비스가 있는데, 무료 한도 너머는 유료. 셀프 호스팅으로 가면 그만큼 일이 늘어남.
6장 · postgres.js·pg·mysql2 — ORM 없이 가기
가장 얇은 길. tagged template과 TypeScript의 추론·satisfies만으로 충분하다는 진영.
// db/index.ts
import postgres from 'postgres'
export const sql = postgres(process.env.DATABASE_URL!, {
max: 10,
idle_timeout: 20,
})
// 쿼리 — tagged template
type User = { id: number; email: string; name: string }
const users = await sql<User[]>`
SELECT id, email, name
FROM users
WHERE email = ${'foo@example.com'}
LIMIT 10
`
// ^ users: User[]
postgres.js는 자체적으로 SQL injection 방지(파라미터 바인딩)와 prepared statement 캐싱을 한다. 타입은 손으로 선언하거나, kysely-codegen 같은 도구로 생성하거나, Zod로 런타임 검증한다.
Zod로 런타임 검증
import { z } from 'zod'
const UserRow = z.object({
id: z.number(),
email: z.string().email(),
name: z.string(),
})
const rows = await sql<unknown[]>`SELECT id, email, name FROM users LIMIT 10`
const users = rows.map((r) => UserRow.parse(r))
// ^ users: z.infer<typeof UserRow>[]
이 패턴의 장점은 DB가 실제로 무엇을 돌려주는지를 한 번 검증한다는 것. ORM·쿼리 빌더의 타입은 컴파일 타임 약속일 뿐, 실제 DB가 약속을 어겼는지(컬럼 NULL·잘못된 타입) 잡지는 못한다.
Raw 클라이언트의 강점
- 가장 작고 가장 빠름 — 추가 추상화 0.
- SQL 거의 전부 사용 가능 — 모든 Postgres 기능 그대로.
- 엣지 호환성 최고 — postgres.js·
pg모두 Cloudflare Workers·Bun에서 잘 돈다. Neon HTTP 어댑터 등은 더 깔끔. - 러닝 커브가 SQL뿐.
Raw 클라이언트의 약점
- 타입 안전은 너의 일.
- 관계 쿼리는 직접 짜야 함.
- 마이그레이션 도구도 직접 골라야 함.
7장 · 같은 쿼리, 네 가지 도구 — "email로 사용자 조회"
가장 단순한 쿼리부터. email로 user 한 명 찾고, 그 user의 최신 post 5개를 같이 가져오기.
7.1 Drizzle
import { db } from './db'
import { users, posts } from './db/schema'
import { eq, desc } from 'drizzle-orm'
async function findUserWithPosts(email: string) {
const user = await db.query.users.findFirst({
where: eq(users.email, email),
with: {
posts: {
orderBy: [desc(posts.id)],
limit: 5,
},
},
})
return user
// user: { id, email, name, createdAt, posts: Array of post rows } or undefined
}
db.query.users.findFirst는 Drizzle Relations API. 내부적으로 한 번의 SQL로 json_agg나 LATERAL join을 만들어 보낸다.
7.2 Kysely
import { db } from './db'
import { jsonArrayFrom } from 'kysely/helpers/postgres'
async function findUserWithPosts(email: string) {
const user = await db
.selectFrom('user')
.where('email', '=', email)
.select((eb) => [
'id',
'email',
'name',
jsonArrayFrom(
eb
.selectFrom('post')
.whereRef('post.author_id', '=', 'user.id')
.orderBy('post.id', 'desc')
.limit(5)
.select(['post.id', 'post.title', 'post.body'])
).as('posts'),
])
.executeTakeFirst()
return user
}
Kysely는 sub-select 헬퍼로 nested JSON을 만든다. jsonArrayFrom은 Postgres json_agg 한 줄 컴파일. SQL이 무엇이 될지 머릿속에 그대로 그려진다.
7.3 Prisma
import { prisma } from './db'
async function findUserWithPosts(email: string) {
const user = await prisma.user.findUnique({
where: { email },
include: {
posts: {
orderBy: { id: 'desc' },
take: 5,
},
},
})
return user
}
읽기 가장 쉽다. Prisma가 내부적으로 알아서 효율적인 쿼리를 만든다(예전엔 N+1 우려가 있었지만 새 엔진에서는 join strategy를 명시적으로 선택할 수 있다).
7.4 postgres.js (raw)
import { sql } from './db'
type UserRow = {
id: number
email: string
name: string
posts: Array<{ id: number; title: string; body: string }>
}
async function findUserWithPosts(email: string) {
const rows = await sql<UserRow[]>`
SELECT
u.id, u.email, u.name,
COALESCE(
json_agg(json_build_object('id', p.id, 'title', p.title, 'body', p.body))
FILTER (WHERE p.id IS NOT NULL),
'[]'::json
) AS posts
FROM users u
LEFT JOIN LATERAL (
SELECT id, title, body
FROM posts
WHERE author_id = u.id
ORDER BY id DESC
LIMIT 5
) p ON TRUE
WHERE u.email = ${email}
GROUP BY u.id
`
return rows[0]
}
길지만 정확히 무엇이 도는지 안다. COALESCE(json_agg(...) FILTER (WHERE ...)) 같은 패턴은 raw에서는 매번 손으로 짜야 한다(헬퍼 만들면 줄어듦).
비교 — 같은 쿼리, 다른 추상화
| 도구 | 줄 수 | 읽기 난이도 | SQL 가시성 | 타입 추론 |
|---|---|---|---|---|
| Drizzle | 짧음 | 쉬움 | 중 | 자동 |
| Kysely | 중 | 중 | 높음 | 자동 |
| Prisma | 가장 짧음 | 가장 쉬움 | 낮음 | 자동 |
| postgres.js | 가장 김 | SQL 의존 | 가장 높음 | 수동/Zod |
8장 · 6축 비교 — 골라잡기 좋게
8.1 추상화 정도
| 도구 | 추상화 정도 |
|---|---|
| postgres.js | 거의 없음 |
| Kysely | SQL 빌더 |
| Drizzle | 스키마 + SQL 빌더 |
| Prisma | 풀 ORM |
| MikroORM·TypeORM | Active Record·Data Mapper |
8.2 마이그레이션 도구
| 도구 | 빌트인 마이그레이션 | 자동 생성 | 비고 |
|---|---|---|---|
| Prisma | prisma migrate | O | 셰도우 DB 필요 |
| Drizzle | drizzle-kit | O | 보수적 자동 생성 |
| Kysely | Kysely 마이그레이션 + 외부 도구 | X | Atlas·dbmate 권장 |
| postgres.js | 없음 | X | Atlas·Sqitch·Flyway 등 |
8.3 엣지 런타임 호환성 (Cloudflare Workers·Vercel Edge·Bun)
| 도구 | 엣지 호환 | 비고 |
|---|---|---|
| Drizzle | 매우 좋음 | 모든 어댑터가 엣지 우선 |
| Kysely | 좋음 | 드라이버 의존, 보통 잘 됨 |
| postgres.js | 좋음 | Neon HTTP·Hyperdrive 결합 시 매우 좋음 |
| Prisma | 좋음 | 새 엔진 이후 안정 |
| MikroORM·TypeORM | 보통~제한 | 데코레이터·reflect-metadata 의존 |
8.4 번들 크기 (압축, 클라이언트 코어만)
대략적인 감(릴리즈에 따라 다름).
| 도구 | 압축 후 크기(대략) |
|---|---|
| postgres.js | 매우 작음 |
| Kysely | 작음 |
| Drizzle | 작음 |
| Prisma | 중간 (새 엔진 이후 큰 폭 축소) |
| MikroORM·TypeORM | 큼 |
8.5 SQL escape hatch
| 도구 | escape hatch | 매끄러움 |
|---|---|---|
| postgres.js | 본인이 SQL | 최고 |
| Kysely | sql 템플릿 | 매우 좋음 |
| Drizzle | sql 템플릿 | 매우 좋음 |
| Prisma | TypedSQL·$queryRaw | 좋음(TypedSQL 이후 정상) |
| MikroORM·TypeORM | raw query | 보통 |
8.6 멀티 DB 지원
| 도구 | Postgres | MySQL | SQLite | 기타 |
|---|---|---|---|---|
| Drizzle | O | O | O | D1·LibSQL·Bun·Neon·Planetscale 외 |
| Kysely | O | O | O | 어댑터 다수 |
| Prisma | O | O | O | SQL Server·MongoDB(제한) |
| postgres.js | O | X | X | Postgres 전용 |
9장 · 실제 마이그레이션 후기 — Prisma → Drizzle / Kysely
9.1 Prisma → Drizzle (가장 흔한 경로)
이 경로를 택하는 팀이 늘었다. 이유는 두 가지.
- 엣지/번들 — 콜드 스타트와 응답 시간에서 차이가 보인다. 새 Prisma 엔진으로 격차는 줄었지만 0은 아니다.
- SQL escape hatch —
sql템플릿이 Prisma의 TypedSQL보다 자연스럽다. TypedSQL은 별도.sql파일이 필요하고, 동적 SQL에는 제약이 있다.
전형적인 마이그레이션 단계.
- 1단계: 새 코드만 Drizzle로. 기존 Prisma 코드는 그대로.
- 2단계:
drizzle-kit으로 기존 DB introspect → 스키마 코드 생성. - 3단계: read 경로부터 점진 치환. write·트랜잭션은 나중에.
- 4단계: 마이그레이션 도구 교체(
prisma migrate→drizzle-kit).
잘 안 되는 부분
- Prisma migrations의 히스토리는 Drizzle Kit이 모른다 — 둘이 한동안 공존하는 동안 충돌이 안 나게 관리해야 함.
- enum·복합 PK·partial index 같은 가장자리 — Drizzle introspection이 100%를 잡지 못하는 경우 있음. 수동 보정 필요.
- 관계 쿼리 의미 미세 차이 — Prisma
include와 Drizzlewith이 같아 보여도 LATERAL join 전략이 다르면 결과 JSON 모양·정렬이 미세하게 다를 수 있음.
9.2 Prisma → Kysely (SQL 팀의 경로)
DB 스키마 운영을 이미 Atlas·Sqitch 같은 도구로 하고 있는 팀, 또는 SQL을 그대로 짜고 싶은데 타입은 받고 싶은 팀이 자주 선택.
- 1단계:
kysely-codegen으로 live DB에서 타입 생성. - 2단계: 가장 복잡한 쿼리(window·CTE·
json_agg)부터 치환 — 가장 큰 가치를 가장 먼저 본다. - 3단계: 마이그레이션은 Atlas·dbmate 등으로 분리(이미 그 단계인 팀이 많음).
- 4단계: read 단순 쿼리도 차차 옮김. write·트랜잭션은 마지막.
잘 안 되는 부분
- 관계 쿼리는 길어진다 — Prisma 한 줄이 Kysely 5~15줄이 되기도. 헬퍼 함수 만드는 일이 필요.
Selectable<DB["user"]>같은 유틸리티 타입 이해 비용 — 처음 보는 사람에게 좀 낯섦.- 에코시스템이 작다 — 플러그인·예제·서드파티 통합이 Prisma만큼 풍성하진 않음.
10장 · MikroORM·TypeORM — 여전히 살아 있지만
MikroORM
엔티티 클래스 기반 Data Mapper ORM. identity map·unit of work·풍성한 관계 API가 강점. DDD 스타일 프로젝트나 도메인 객체 중심으로 짜는 팀이 좋아한다. SQL과 거리는 가장 멀다.
- 강점: 도메인 객체 중심, Active Record/Data Mapper 모두 지원, 트랜잭션·관계가 매끄럽다.
- 약점: 무겁다, 데코레이터·
reflect-metadata의존, 엣지 친화도 보통.
TypeORM
가장 오래된 TS ORM 중 하나. 데코레이터 기반 Active Record + Data Mapper. 유지보수가 일정치 않고 알려진 버그가 오래 남는 경향이 있어 신규 프로젝트의 첫 선택지는 아니다. 그러나 이미 TypeORM으로 짠 코드가 많으면 빨리 떼지 못한다.
- 강점: 친숙함, 풍부한 데코레이터 API.
- 약점: 유지보수 risk, TS 5.x 변화·데코레이터 표준 변경에 둔감, 엣지 호환 어려움.
신규 프로젝트에서 TypeORM을 자발적으로 고르는 경우는 2026년에 드물다. 이미 있는 코드베이스는 마이그레이션 비용·이득을 따로 따져야 한다.
11장 · 무엇을 언제 골라야 하나 — 솔직한 결론
자, 정답은? 항상 그렇듯 "상황 따라 다르다". 다만 패턴이 있다.
Drizzle을 고를 때
- 신규 프로젝트, 엣지 런타임(Cloudflare Workers·Vercel Edge) 우선.
- SQL을 알고/배우려는 팀.
- 멀티 DB(SQLite·Postgres·MySQL·D1·LibSQL) 가능성.
- 번들 크기·콜드 스타트가 중요.
Kysely를 고를 때
- 마이그레이션은 이미 Atlas·Sqitch·dbmate 등으로 운영.
- 복잡한 SQL(CTE·window·
json_agg)이 많고, SQL을 직접 짜고 싶음. - 스키마 관리·DDL을 ORM과 분리하고 싶음.
- 작은 의존성·작은 번들을 원함.
Prisma를 고를 때
- 신규 풀스택 팀, 학습 곡선 최소화 우선.
- 관계가 복잡하고
include/페치 매끄러움이 중요. - Prisma Postgres·Accelerate·Pulse 같은 매니지드 도구체인을 활용하고 싶음.
- TypedSQL로 복잡 SQL을 따로 다뤄도 괜찮음.
postgres.js·pg(raw)를 고를 때
- 팀 전체가 SQL에 강함.
- 추가 추상화를 의식적으로 거부.
- 가장 작은 번들·가장 빠른 런타임이 핵심.
- 런타임 검증(Zod)이나 codegen으로 타입을 별도 관리 가능.
안티 체크리스트 (피해야 할 패턴)
- 한 프로젝트에 ORM 두 개 — 학습·디버그 비용 2배.
- "Prisma 느려서 Drizzle로 갈아탔다"는 결정을 측정 없이 내림 — 새 엔진 이후엔 적용 안 되는 경우 많음.
- ORM 골라놓고 거의 모든 쿼리를
$queryRaw로 짬 — 그러면 ORM이 아니라 무거운 raw 클라이언트. - 스키마는 Drizzle, 마이그레이션은 Prisma 같은 어색한 조합 — 진실의 원천이 두 개.
- 엣지 우선 환경인데 데코레이터 ORM 채택 — 빌드·번들·런타임 모두 가시밭.
에필로그 — 추상화는 도구이지 종교가 아니다
ORM·쿼리 빌더 선택은 추상화 정도와 escape hatch 매끄러움의 트레이드오프다. 한쪽이 우월하지 않다. 다만 2026년 5월의 풍경은 분명하다.
- SQL에 더 가까운 쪽이 늘었다 — Drizzle·Kysely·raw 진영이 자랐다.
- 풀 ORM은 다이어트 중 — Prisma 새 엔진과 TypedSQL은 약점 두 개를 정통으로 친다.
- 엣지는 기본값이 됐다 — 어떤 도구를 골라도 엣지 호환을 무시할 수 없다.
- '한 도구가 모든 걸 해결'은 점점 깨졌다 — DB 클라이언트 + 마이그레이션 도구 + 타입 생성 도구를 따로 조합하는 게 흔해졌다.
결정 체크리스트
- 엣지 우선인가? — Yes면 Drizzle·Kysely·postgres.js·새 Prisma.
- 팀이 SQL에 강한가? — Yes면 Kysely·postgres.js, No면 Prisma·Drizzle.
- 관계 페치가 핵심인가? — Yes면 Prisma > Drizzle Relations > Kysely.
- 복잡 SQL(CTE·window)이 많은가? — Yes면 Kysely·postgres.js > Drizzle > Prisma TypedSQL.
- 마이그레이션 도구는? — Prisma·Drizzle Kit 빌트인 vs Atlas·dbmate 외부.
- 멀티 DB 가능성은? — Yes면 Drizzle·Kysely.
- 학습 곡선 최소화? — Prisma.
안티 패턴
- 측정 없이 ORM 갈아타기 — 결정은 측정 후에.
- 한 코드베이스에 ORM 둘 공존 — 종착지 정하고 마이그레이션 일정 잡기.
- ORM 쓰면서
$queryRaw90% — ORM이 아니라 무거운 raw. - 스키마 진실의 원천 두 개 — DB와 코드의 진실은 하나여야.
- 마이그레이션 자동화 없이 codegen — 타입이 거짓말한다.
- 엣지 우선인데 무거운 데코레이터 ORM — 빌드·런타임 둘 다 비용.
- 관계가 복잡한데 raw — 직접 짜야 할 SQL이 산더미.
다음 글 예고
다음 글 후보: Drizzle Relations v2 깊게 — json_agg·LATERAL join이 어떻게 만들어지는가, Prisma TypedSQL 한 달 써본 후기 — 어디에 쓰고 어디에 안 쓰나, Cloudflare Workers + Hyperdrive + postgres.js 풀스택 사례.
"SQL을 알면 모든 도구가 친절해진다. 모르면 어떤 도구도 마법이 아니다."
— TypeScript ORM·쿼리 빌더 2026, 끝.
참고 / References
- Drizzle ORM 공식
- Drizzle ORM GitHub
- Drizzle Kit Documentation
- Drizzle Relations API
- Kysely 공식
- Kysely GitHub
- kysely-codegen
- Prisma 공식
- Prisma TypedSQL Documentation
- Prisma Postgres
- Prisma 엔진 재작성 — 공식 블로그
- postgres.js GitHub — porsager/postgres
- node-postgres (pg) 공식
- mysql2 GitHub
- MikroORM 공식
- TypeORM 공식
- Neon — Serverless Postgres
- Cloudflare Hyperdrive
- Cloudflare D1
- Atlas — Modern DB migration
- Vercel Postgres
- Zod
- Hono — Edge web framework
- T3 Stack