🗄️ TypeORM0. TypeORM의 기초03 — TypeORM vs Other ORMs

03 — TypeORM vs Other ORMs

이 문서가 답하는 질문: TypeORM은 Prisma·Drizzle·MikroORM·Sequelize·Kysely와 정확히 어디가 다른가? 같은 카테고리에서 경쟁하는 게 맞나? 한 줄 답 (Pyramid Top): “이 다섯은 같은 자리가 아니라 다른 트레이드오프를 산다 — Prisma는 스키마 우선, Drizzle은 SQL 우선, MikroORM은 Unit of Work 우선, Sequelize는 JS 시대 유산, Kysely는 ORM이 아닌 query builder.”

자세한 매트릭스는 08-theory-and-alternatives에서. 여기서는 개념적 위치 차이만 본다.


한 문장 답 (Pyramid Top)

‘TypeScript ORM’이라는 한 카테고리 안에 다섯이 들어가 있는 듯 보이지만 — 실제로는 철학의 출발점이 다 다르다. ① TypeORM: 클래스 + 데코레이터에서 시작 (declaration 우선), ② Prisma: schema.prisma DSL에서 시작 (schema 우선), ③ Drizzle: SQL 쿼리 모양에서 시작 (SQL 우선), ④ MikroORM: Identity Map + Unit of Work에서 시작 (Hibernate 우선), ⑤ Sequelize: 모델 정의 함수에서 시작 (JS 시대 유산), ⑥ Kysely: 타입드 SQL builder에서 시작 (ORM이 아님).


챕터 지도 (Mermaid)

여섯이 같은 문제에 대답하지만, 출발점과 잃는 것이 모두 다르다.


Why — 왜 이 비교가 의미 있나

“어떤 ORM을 쓸까”라는 질문이 잘못된 질문일 때가 많다. 진짜 질문은 *“내 팀이 어디서 가장 자주 다치는가”*다.

자주 다치는 곳가장 잘 맞는 도구
마이그레이션·스키마 변경이 너무 잦다Prisma (schema-first가 강함)
복잡한 SQL이 자주 필요하다Drizzle / Kysely
도메인 객체의 행동·invariant이 풍부하다MikroORM / TypeORM
데코레이터 메타프로그래밍이 익숙하다TypeORM / MikroORM
기존 JS 코드와 점진적으로 통합해야 한다Sequelize (오래된 호환)
컴파일 타임에 SQL을 검증하고 싶다Drizzle / Kysely

이 표가 비교의 본질이다 — 기능 매트릭스가 아니라 고통의 매트릭스.


How — 어떻게 다른가

1) 같은 User 테이블을 다섯 가지로 표현

// (1) TypeORM — 클래스 + 데코레이터
@Entity()
export class User {
  @PrimaryGeneratedColumn() id!: number;
  @Column() name!: string;
  @OneToMany(() => Post, p => p.user) posts!: Post[];
}
// (2) Prisma — schema.prisma DSL
model User {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}
// (3) Drizzle — SQL DSL
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: text("name").notNull(),
});
 
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));
// (4) MikroORM — 데코레이터 (TypeORM과 비슷하나 EntityManager가 UoW)
@Entity()
export class User {
  @PrimaryKey() id!: number;
  @Property() name!: string;
  @OneToMany(() => Post, p => p.user) posts = new Collection<Post>(this);
}
// (5) Sequelize — 모델 함수
const User = sequelize.define("User", {
  id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  name: { type: DataTypes.STRING },
});
User.hasMany(Post);
// (6) Kysely — 타입드 query builder (모델 선언 없음, 인터페이스만)
interface Database {
  users: {
    id: Generated<number>;
    name: string;
  };
  posts: { /* ... */ };
}
// 실제 사용은 db.selectFrom("users").select(...).execute()

같은 데이터를 완전히 다른 추상으로 잡는다.

2) Single Source of Truth가 어디 있나

ORM마다 진실의 출처가 다르다 — 이 결정이 마이그레이션 워크플로 전체를 결정한다.

  • TypeORM·MikroORM: 클래스가 진실migration:generate로 SQL 산출
  • Prisma: schema.prisma가 진실prisma migrate로 SQL 산출 + TS 클라이언트 자동 생성
  • Drizzle: TS 스키마 파일이 진실drizzle-kit으로 SQL 산출
  • Kysely: DB 자체가 진실 → 사용자가 직접 인터페이스 작성 (또는 introspect 도구)

3) 타입은 어디서 오나

도구타입의 출처강점약점
TypeORM데코레이터 단 클래스 = 그대로 타입클래스 한 줄로 모든 게 끝남동적 select/join에서 타입이 잘 안 좁혀짐
Prismaschema.prisma → 코드젠 → @prisma/clientfind의 select 옵션까지 정확히 타입코드젠 단계가 빌드에 끼임
DrizzleSQL DSL이 그대로 타입컴파일 타임에 SQL 검증학습 곡선 (SQL을 TS로 다시 써야)
MikroORM데코레이터 = 타입 (TypeORM과 유사)Identity Map으로 같은 row는 같은 객체데코레이터 의존 동일
SequelizeModel.init()의 attributes옛날 자료가 많음TS 추론이 약하고 보일러플레이트가 많음
Kysely사용자 정의 인터페이스raw SQL에 가장 가까운 타입 안정성관계·자동 join 없음 (ORM 아니니까)

4) ‘Unit of Work’를 가지는가

Hibernate 스타일의 Unit of Work는 한 트랜잭션 안에서 변경된 객체를 자동 추적해 flush 시점에 일괄 SQL을 만든다.

도구UoW의미
TypeORM없음repo.save() 명시 필요
Prisma없음prisma.user.update() 명시 필요
Drizzle없음SQL DSL이라 UoW 개념 없음
MikroORM있음em.persist(u); ... await em.flush() — Hibernate 스타일
Sequelize없음instance.save() 명시 필요
Kysely없음query builder라 무관

UoW의 유무가 큰 도메인 코드를 짤 때의 체감 차이를 만든다.


What — 구체 사양

위치 차이 매트릭스 (개념 수준)

TypeORMPrismaDrizzleMikroORMSequelizeKysely
출발 추상클래스스키마 DSLSQL DSL클래스 + UoW모델 함수인터페이스
타입 출처데코레이터코드젠DSL 자체데코레이터모델 정의사용자 정의
마이그레이션generate (불완전)migrate dev (강함)drizzle-kitgenerateumzug 별도직접 작성
DataMapperO (권장)△ (단일 인스턴스 없음)X (no ORM)OX
ActiveRecordO (호환)XXXOX
Unit of WorkXXXOXX
컴파일 타임 SQL 검증약함부분 (typed query)강함약함약함강함
N+1 자동 해결부분 (relations)자동 (include)수동부분수동수동
데코레이터 의존강함없음없음강함없음없음
GitHub 스타 (대략)33k+38k+25k+7k+29k+11k+

(별표 수치는 시점에 따라 다름)

같은 ‘find one with relation’ 비교

// TypeORM
const user = await userRepo.findOne({
  where: { id: 1 },
  relations: { posts: true }
});
// Prisma
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true }
});
// → user.posts의 타입까지 정확히 추론됨
// Drizzle
const result = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: { posts: true }
});
// MikroORM
const user = await em.findOne(User, 1, { populate: ['posts'] });
// Sequelize
const user = await User.findByPk(1, { include: [Post] });
// Kysely (ORM 아님 — 직접 join)
const rows = await db
  .selectFrom("users")
  .leftJoin("posts", "posts.userId", "users.id")
  .where("users.id", "=", 1)
  .selectAll()
  .execute();
// 결과는 평면 row[], 직접 그룹핑 필요

API의 시그니처가 닮아 보이지만, 생성되는 SQL과 타입의 안정성은 모두 다르다.


What-if — 잘못 비교하면

1) “Prisma가 더 좋다더라”로 무작정 갈아타면

→ Prisma는 코드젠 단계가 빌드 파이프라인에 강제로 끼인다 — Docker·CI·모노레포 환경에서 예상 못한 비용이 생길 수 있다. 대응: PoC로 한 모듈 옮겨보고, 마이그레이션 워크플로 + 빌드 시간을 같이 측정한다.

2) “Drizzle은 가벼우니 무조건 좋다”로 잡으면

→ Drizzle은 SQL을 TS로 다시 써야 한다 — 큰 도메인 모델·풍부한 관계·자동 join이 필요한 곳에서는 코드량이 오히려 늘 수 있다. 대응: 도메인이 얇고 SQL이 자주 바뀌는 곳에서만 Drizzle이 이긴다 (analytics·report 등).

3) “Kysely도 ORM 아닌가” 하면

→ Kysely는 typed query builder다. 엔티티·관계·Repository 개념이 없다. 대응: Kysely는 raw SQL을 안전하게 쓰는 도구이지 ORM 대체재가 아니다. 같은 위치에 놓고 비교하면 안 된다.

4) “MikroORM = TypeORM”이라고 보면

→ 둘 다 데코레이터 ORM이지만 Unit of Work의 유무가 코드 흐름을 완전히 바꾼다. 대응: 큰 도메인 + 트랜잭션 무거운 작업이면 MikroORM이 더 적합할 수 있음을 알고 있어야 한다.

5) “Sequelize는 끝났다”고 단언하면

→ Sequelize는 2010년대 JS 시대의 코드베이스에 여전히 살아 있다. TS 추론이 약하지만 마이그레이션 비용이 더 클 수 있다. 대응: 신규 프로젝트는 권하지 않지만, 기존 Sequelize 코드는 그대로 두는 결정도 합리적이다.


Insight — 흥미로운 이야기

”데코레이터 두 ORM은 다른 길을 갔다”

TypeORM과 MikroORM은 둘 다 데코레이터 ORM이지만, MikroORM은 Hibernate를 더 충실히 옮겼다EntityManager + Identity Map + Unit of Work까지. 2018년 Martin Adámek가 MikroORM을 시작할 때, 그가 본 TypeORM의 한계는 명시적 save() 호출이 큰 도메인에서 누락되기 쉽다는 것이었다. 5년이 지난 지금, 두 ORM은 *같은 표면(데코레이터)에 다른 추상(UoW 유무)*을 얹어 서로 다른 청중에 닿아 있다.

”Prisma의 ‘코드젠’은 사상 차이다”

Prisma가 코드젠 단계를 강제하는 건 단순한 구현 선택이 아니다 — “클래스에 메타를 박는 게 아니라, 스키마를 진실로 두고 코드는 산출물로 본다” 는 사상 표현이다. 이 결정이 *Prisma의 강점(타입 정확도)과 약점(빌드 결합)*을 동시에 만든다. TypeORM이 런타임 메타에 거는 동안, Prisma는 컴파일 타임 코드젠에 걸었다.

”Drizzle의 빠른 부상”

Drizzle는 2022년에 등장해 2년 만에 GitHub 스타 25k를 넘긴 가장 빠른 ORM 부상 사례다. 이유는 “ORM의 추상은 다 거짓말이다”라는 정서가 누적됐기 때문이다 — N+1·이상한 SQL·디버깅 어려움 등. Drizzle는 “SQL을 그대로 쓰되 타입만 얹자” 는 메시지로 그 정서에 정확히 답했다. TypeORM의 위협은 기능이 아니라 사상의 피로에서 온다.


요약 + 다이어그램

여섯 ORM·도구는 같은 카테고리에 있지 않다. 출발 추상·타입 출처·마이그레이션 워크플로·Unit of Work 유무가 모두 다르다. “어떤 게 좋은가”가 아니라 “내 팀이 어디서 다치는가”가 진짜 질문이다.

다음 문서: 04-decorator-and-metadata.mdx — TypeORM의 차이의 정체인 데코레이터 한 줄은 어떻게 동작하나?