05 — TypeORM vs Sequelize (JS 시대 vs TS 시대)
한 줄 답: Sequelize는 2010년 JavaScript 시대에 태어난 ActiveRecord 스타일 ORM이고, TypeORM은 2016년 TypeScript 시대에 태어난 DataMapper 스타일 ORM이다. 타입 지원·패턴·생태계가 근본적으로 다르다. 레거시 Sequelize 코드베이스가 아니라면 새 TypeScript 프로젝트에서 Sequelize를 선택할 이유는 거의 없다.
Why — 왜 시대 차이가 이렇게 큰가
| 시기 | 사건 |
|---|---|
| 2010 | Sangria Ostowski가 Sequelize 시작 — Node.js + JavaScript 시대 |
| 2011 | Sequelize 1.0 — ActiveRecord 패턴 (Rails 영향) |
| 2014 | TC39 Decorator Stage 1 제안서 등장 |
| 2014 | Microsoft TypeScript 1.0 |
| 2016 | Umed Khudoiberdiev TypeORM 시작 — decorator + reflect-metadata + DataMapper |
| 2018 | NestJS 등장 — TypeORM이 사실상 표준화 |
| 2020 | Sequelize TypeScript 지원 강화 시도 (v6) — 부분적 성공 |
| 2023 | Sequelize 7 alpha — TypeScript 완전 재작성 (alpha 단계 길게 지속) |
| 2024 | TypeORM ~33k stars, Sequelize ~29k stars (Sequelize는 완만한 정체) |
Sequelize는 TypeScript 시대로 갈아타려는 중이지만 — 2024년 현재도 v7은 alpha다. TypeORM은 처음부터 TypeScript로 태어났다는 근본 차이가 메우기 어렵다.
How — 두 도구의 근본 차이
1) 모델 정의
Sequelize (v6, 일반적 사용):
import { Sequelize, DataTypes, Model } from 'sequelize'
interface UserAttributes {
id: number
email: string
name?: string
}
class User extends Model<UserAttributes> {
declare id: number
declare email: string
declare name?: string
}
User.init({
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING, allowNull: false },
name: { type: DataTypes.STRING, allowNull: true },
}, { sequelize, modelName: 'User' })- 클래스 정의와 Model.init 호출과 interface가 3중으로 필요.
- 타입과 런타임 스키마가 분리되어 있어 동기화가 사용자 책임.
TypeORM:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
class User {
@PrimaryGeneratedColumn()
id!: number
@Column()
email!: string
@Column({ nullable: true })
name?: string
}- 클래스 한 곳에 모든 것. 데코레이터가 타입과 스키마를 동시에 표현.
2) 패턴 차이 — ActiveRecord vs DataMapper
Sequelize (ActiveRecord):
const user = await User.findByPk(1)
user.email = 'x@x.com'
await user.save() // ← 객체가 *자기* save를 호출TypeORM (DataMapper):
const user = await userRepo.findOne({ where: { id: 1 } })
user.email = 'x@x.com'
await userRepo.save(user) // ← *외부 게이트웨이*가 save3) 타입 정확도
Sequelize:
const user = await User.findOne({
where: { email: 'x@x.com' },
attributes: ['id', 'email']
})
// user의 타입은? — User | null (attributes 효과 *반영 안 됨*)
console.log(user?.name) // ← TS 에러 없음. 런타임에 *undefined*TypeORM:
const user = await userRepo.findOne({
where: { email: 'x@x.com' },
select: { id: true, email: true }
})
// user의 타입은? — User | null (select 효과 *반영 안 됨*)
// ← 비슷한 한계지만 *fields 추론은 TypeORM이 살짝 더 시도함*판정: *Sequelize의 타입은 v6까지 부분적. v7 alpha에서 개선 중이지만 정식 출시 전. TypeORM도 부분이지만 더 낫다. 둘 다 Prisma만큼 정확하진 않다.
What — 구체적 시나리오로 비교
시나리오 A — 새 TypeScript 프로젝트
Sequelize:
interface+class+Model.init3중 정의- v6의
declare키워드 boilerplate 많음 - v7 alpha를 쓸지 말지 고민
TypeORM:
- 데코레이터 한 곳에 표현
- NestJS 공식 통합
- 자료 풍부
판정: TypeORM 압승. 새 TS 프로젝트에 Sequelize를 선택할 이유는 거의 없다.
시나리오 B — 레거시 JavaScript 코드베이스
Sequelize:
- 기존 코드가 그대로 동작
- gradual TypeScript 도입 가능
TypeORM:
- JS 코드를 모두 TS로 옮기는 작업 필요
- 데코레이터는 기본적으로 JS에서 잘 안 됨 (Babel 플러그인 필요)
판정: Sequelize 유지가 합리적. 큰 JS 코드베이스의 점진 이주는 Sequelize가 더 부드럽다.
시나리오 C — Rails/Django 출신 개발자
Sequelize: ActiveRecord 패턴이 Rails ActiveRecord와 거의 같다. user.save()가 자연스럽다.
TypeORM: DataMapper 패턴이 Repository를 한 번 더 거친다. Rails 개발자에게는 낯설다.
판정: Rails 출신 팀이면 Sequelize가 학습 비용이 낮다. 하지만 큰 코드베이스에서 결합 폭발은 ActiveRecord의 알려진 한계.
시나리오 D — 마이그레이션 도구
Sequelize:
npx sequelize-cli migration:generate --name add-user-age
# ← *수동으로* SQL 작성. 자동 generate 없음.TypeORM:
npm run typeorm migration:generate -- -n AddUserAge
# ← 엔티티 diff로 *자동 SQL 생성*판정: TypeORM 우세. Sequelize는 Umzug에 의존하는데 자동 생성 기능이 약하다.
시나리오 E — 트랜잭션
Sequelize:
await sequelize.transaction(async (t) => {
const user = await User.findByPk(1, { transaction: t })
// ← *모든 쿼리에 { transaction: t } 명시적으로* 전달해야 함 (CLS 옵션 켜야 자동)
user.email = 'x@x.com'
await user.save({ transaction: t })
})TypeORM:
await dataSource.transaction(async (manager) => {
const user = await manager.findOne(User, { where: { id: 1 } })
user.email = 'x@x.com'
await manager.save(user)
// ← *manager가 transaction context를 자동 관리*
})판정: TypeORM이 더 깔끔. Sequelize는 transaction 전달의 boilerplate가 많다 (CLS namespace 옵션을 켜면 개선되지만 추가 설정).
What-if — 잘못 선택하면
1) 새 TS 프로젝트인데 Sequelize
→ interface + class + Model.init 3중 정의의 boilerplate. TS 타입과 런타임 스키마 동기화 책임이 개발자에게.
대응: TypeORM 또는 Prisma.
2) Rails 출신 팀인데 무리하게 TypeORM
→ DataMapper의 userRepo.save(user)가 낯설어 코드가 Rails 스타일로 작성됨 — BaseEntity 상속 남용.
대응: TypeORM의 ActiveRecord 모드를 켜거나 Sequelize 유지.
3) Sequelize v7 alpha를 production에
→ 2024년 현재도 alpha. breaking change가 남아 있다. production 위험. 대응: v6 유지 또는 다른 ORM으로 이주.
4) 큰 코드베이스에서 ActiveRecord 패턴 남용
→ 도메인 객체가 영속성을 안다. 단위 테스트 DB 필요, 결합 폭발. 대응: Sequelize라도 Repository 레이어를 직접 짜 DataMapper 흉내.
5) “Sequelize도 충분히 type-safe”라고 과신
→ findAll<...>의 제네릭 누수, attributes 옵션의 타입 미반영. production 런타임 에러.
대응: 진지한 타입 정확도가 필요하면 Prisma. 부분 타입으로 충분하면 TypeORM.
Insight — 흥미로운 이야기
”Sequelize의 14년”
Sequelize는 2010년 시작해 14년째 운영 중이다. Node.js의 거의 모든 시기를 살아냈다 — CommonJS 시대, ES5 시대, Promise 도입, async/await 도입, TypeScript 부상.
이 오래 살아남음이 매력이면서 부담이다. 과거 API 호환을 유지해야 해서 근본 재설계가 어렵다. v7이 완전 재작성인 이유 — 하지만 그 재작성이 alpha 단계만 2년 지속 중인 이유이기도.
”TypeORM은 Sequelize 후속이 아니다”
흔한 오해 중 하나는 *“TypeORM이 Sequelize의 TypeScript 버전”*이라는 것. 틀렸다. TypeORM은 *Hibernate (Java) + Doctrine (PHP)*의 DataMapper 계보에서 왔다. Sequelize는 Rails ActiveRecord 계보에서 왔다.
두 도구는 DNA가 다른 ORM이다. 데코레이터 차이보다 DataMapper vs ActiveRecord의 패턴 차이가 더 본질적.
”Sequelize v7의 재탄생 시도”
Sequelize v7 (2022~2024 alpha)은 완전 TypeScript 재작성이고 — decorator 기반 모델 정의를 옵션으로 추가한다. 이게 무엇을 닮았나? TypeORM. 그리고 MikroORM.
// Sequelize v7 (alpha) — decorator 스타일
import { Attribute, NotNull } from '@sequelize/core/decorators-legacy'
class User extends Model {
@Attribute(DataTypes.INTEGER)
@PrimaryKey
@AutoIncrement
declare id: number
@Attribute(DataTypes.STRING)
@NotNull
declare email: string
}시장이 옳다고 본 것이 데코레이터였고 Sequelize도 결국 그 길을 따른다. 하지만 후발 주자의 한계가 명확하다 — 기존 v6 사용자를 어떻게 끌고 갈 것인가가 공개 이슈다.
요약 + 다이어그램
TypeORM vs Sequelize = TS 시대 vs JS 시대. Sequelize는 Rails ActiveRecord의 후예, TypeORM은 Hibernate DataMapper의 후예 — DNA가 다르다. 새 TypeScript 프로젝트면 TypeORM 또는 Prisma, 레거시 JS 코드베이스면 Sequelize 유지.
다음 문서:
06-when-not-to-use-typeorm.mdx— 그럼 TypeORM을 쓰지 말아야 하는 자리는?