[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
---
|
||||
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<DB>({ 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<User[]>`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<User[]>`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]]
|
||||
Reference in New Issue
Block a user