--- id: db-orm-comparison title: ORM 비교 — Prisma / Drizzle / Kysely / TypeORM category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [database, orm, prisma, drizzle, kysely, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Backend"] } applied_in: [] aliases: [Prisma, Drizzle, Kysely, TypeORM, Sequelize, MikroORM, query builder] --- # ORM 비교 > **Prisma = 강 type 안전 + DX, Drizzle = 가볍고 SQL 그대로, Kysely = pure query builder, TypeORM = 옛 ActiveRecord**. 추천 = Drizzle (modern) 또는 Prisma (DX). ## 📖 핵심 개념 - ORM: object 매핑. - Query builder: SQL 같은 chained API. - Type-safe: schema → type infer. - Migration: schema 변경 자동 SQL. ## 💻 코드 패턴 ### Prisma ```prisma // schema.prisma model User { id String @id @default(uuid()) email String @unique posts Post[] } model Post { id String @id @default(uuid()) title String userId String user User @relation(fields: [userId], references: [id]) } ``` ```ts import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); const u = await prisma.user.findUnique({ where: { id }, include: { posts: true }, }); await prisma.user.create({ data: { email, posts: { create: [{ title: 'first' }] } }, }); await prisma.user.update({ where: { id }, data: { email: newEmail }, }); ``` → Pros: 강 type, migration 자동. Cons: query engine binary, 일부 제약. ### Drizzle (modern, SQL-style) ```ts import { pgTable, uuid, text, integer } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), email: text('email').unique().notNull(), }); export const posts = pgTable('posts', { id: uuid('id').primaryKey().defaultRandom(), title: text('title').notNull(), userId: uuid('user_id').references(() => users.id), }); ``` ```ts import { drizzle } from 'drizzle-orm/node-postgres'; import { eq } from 'drizzle-orm'; const db = drizzle(pool); const result = await db.select().from(users).where(eq(users.id, id)); await db.insert(users).values({ email }); await db.update(users).set({ email: newEmail }).where(eq(users.id, id)); // Joined const result = await db .select() .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .where(eq(users.id, id)); ``` → Pros: 가볍고 빠름, SQL 그대로. Cons: 약간 verbose. ### Kysely (pure query builder) ```ts import { Kysely, PostgresDialect } from 'kysely'; interface DB { users: { id: string; email: string }; posts: { id: string; title: string; user_id: string }; } const db = new Kysely({ dialect: new PostgresDialect({ pool }) }); const u = await db.selectFrom('users').where('id', '=', id).selectAll().executeTakeFirst(); await db.insertInto('users').values({ id: uuid(), email }).execute(); const joined = await db .selectFrom('users') .leftJoin('posts', 'posts.user_id', 'users.id') .where('users.id', '=', id) .selectAll() .execute(); ``` → Pros: SQL 정확 매핑, 매우 typed. Cons: schema 직접 정의. ### TypeORM (옛, ActiveRecord) ```ts @Entity() class User { @PrimaryGeneratedColumn('uuid') id!: string; @Column({ unique: true }) email!: string; @OneToMany(() => Post, p => p.user) posts!: Post[]; } const u = await User.findOne({ where: { id }, relations: ['posts'] }); ``` → 옛 design, 새 프로젝트 권장 X. ### Migration 비교 ```bash # Prisma prisma migrate dev --name add_user_email prisma migrate deploy # Drizzle drizzle-kit generate drizzle-kit migrate # Kysely # 직접 SQL 파일 + kysely-codegen 으로 type ``` ### Transaction ```ts // Prisma await prisma.$transaction(async (tx) => { await tx.user.create({...}); await tx.post.create({...}); }); // Drizzle await db.transaction(async (tx) => { await tx.insert(users).values(...); }); // Kysely await db.transaction().execute(async (tx) => { await tx.insertInto('users').values(...).execute(); }); ``` ### Raw SQL (escape hatch) ```ts // Prisma const result = await prisma.$queryRaw`SELECT * FROM users WHERE name LIKE ${pattern}`; // Drizzle import { sql } from 'drizzle-orm'; const r = await db.execute(sql`SELECT * FROM users WHERE name LIKE ${pattern}`); // Kysely const r = await sql`SELECT * FROM users WHERE name LIKE ${pattern}`.execute(db); ``` ### Connection pool ```ts // 모두 공통: pg / mysql 의 Pool wrap import { Pool } from 'pg'; const pool = new Pool({ connectionString, max: 20 }); ``` ### Edge runtime ``` Drizzle: HTTP driver (Neon / PlanetScale) 가벼움. Edge OK. Prisma: Accelerate / Data Proxy 필요. 기본은 binary (Edge X). Kysely: HTTP driver 호환. ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 빠른 시작 + DX | Prisma | | Edge runtime | Drizzle / Kysely | | 가벼운 + SQL 명확 | Drizzle | | Pure query builder | Kysely | | 매우 작은 bundle | Drizzle | | 큰 schema + relation 복잡 | Prisma 또는 Drizzle | | 옛 시스템 유지 | TypeORM | ## ❌ 안티패턴 - **N+1 ORM**: include 안 쓰고 loop 안 쿼리. Prisma include / Drizzle leftJoin. - **모든 column SELECT**: select 명시. - **Connection pool 매번 새로**: lazy singleton. - **Migration 수동 prod**: drift. CI 에서 auto. - **Transaction 안 외부 API**: 롤백 시 부수효과만 남음. - **Raw SQL string concat**: SQL injection. parameterized. - **Schema 두 곳 (.prisma + ts type)**: drift. infer. ## 🤖 LLM 활용 힌트 - 새 프로젝트 = Drizzle 또는 Prisma. - Edge / serverless = Drizzle (Neon HTTP). - Prisma = DX 우월하지만 binary. ## 🔗 관련 문서 - [[DB_Migration_Safety]] - [[DB_N_Plus_One]] - [[DB_Connection_Pool]]