알려진 문제들

이 문서가 답하는 질문: TypeORM을 운영하는 팀이 공통적으로 부딪히는 통증은 무엇이고, 각각의 원인은 코드인가 생태계인가 spec인가? 한 줄 답 (Pyramid Top): “통증의 출처는 세 층이다 — 코드(findOne deprecated, save가 upsert), 생태계(ESM 미지원, NestJS 강결합), 외부 spec(데코레이터 9년째 stage 2~3). 코드 통증은 패치 가능하지만, 아래 두 층은 사용자가 풀 수 없다.”


한 문장 답 (Pyramid Top)

통증을 카탈로그화해서 분류하면 의사결정이 쉬워진다 — 팀이 풀 수 있는 통증인가, TypeORM 메인테이너만 풀 수 있는 통증인가, TC39 전체가 움직여야 풀리는 통증인가. 후자 두 개에 묶여 있다면 이주가 합리적이고, 첫 번째라면 패치로 살 수 있다.


챕터 지도


Why — 왜 카탈로그가 필요한가

운영 중인 팀의 오해는 보통 이렇게 생긴다 — “TypeORM이 느려서 옮긴다”, “TypeORM이 별로라서 옮긴다”. 정확하지 않다. 실제 동기는 구체적인 통증이고, 그 통증마다 해결 가능성해결자가 누구인가가 다르다. 카탈로그를 만들면 내가 풀 수 있는 통증못 푸는 통증이 분리되고, 후자가 임계점을 넘었을 때만 이주가 합리적인 선택이 된다.


How — 어떻게 분류하나

세 층으로 본다.

해결자사용자가 할 수 있는 일
L1 · 코드팀 자신wrapper, 컨벤션, 린트 룰
L2 · 생태계TypeORM 메인테이너issue 보이스, PR 기여, fork
L3 · specTC39사실상 불가능 — 대안 ORM 사용

What — 통증 카탈로그

L1 — 코드 통증 (사용자가 패치 가능)

통증증상우회
findOne() deprecated 사고0.2 → 0.3 전환에서 findOne(id) 시그니처가 조용히 바뀌어 — 1번 레코드 대신 첫 레코드를 반환. 일부 팀은 프로덕션에서 발견.findOneBy({ id }) 명시, 0.3 migration guide 준수, findOne(id) 사용 금지 ESLint 룰
save()가 upsertsave()항상 SELECT 선행 → 알 수 없는 추가 쿼리. 대량 insert에서 수십 배 느림.insert() / bulk insert 사용, save는 단건 + 동기화 자리만
타입 정확도 부분적find({ select })로 일부 필드만 골라도 반환 타입은 전체 엔티티 — 컴파일러가 없는 필드 접근을 잡지 못함.DTO 매핑 강제, select 결과는 Partial<T>로 캐스팅
@nestjs/typeorm request-scoped 누수request-scoped provider에서 transaction을 안전하게 묶기 어려움 — 잘못된 패턴이 공식 docs에도 등장한 적 있음.QueryRunner명시적으로 주입, transaction은 service에서만 시작

L2 — 생태계 통증 (메인테이너만 해결 가능)

통증증상우회 (가능하다면)
ESM 미지원TypeORM은 CJS 기반. "type": "module" 환경에서 데코레이터 메타데이터가 깨지는 케이스 다수.typeorm-ts-node-commonjs runner, dual package, 또는 패키지 전체를 CJS로
메인테이너 활동 정체2020~2022년 18개월 동안 큰 PR 머지 없음. 핵심 이슈가 수년 단위로 열려 있음.자신의 fork에서 패치 (실제로 일부 팀이 함), 또는 다른 ORM
NestJS와의 강결합docs/예제가 NestJS 위주 — 다른 프레임워크에서 best practice 찾기 어려움.Express/Fastify 직접 통합 예제는 커뮤니티 블로그에 산재
마이그레이션 generate 누락인덱스 변경, enum 변경, 일부 제약 변경이 generate에 잡히지 않음.generate 결과를 항상 사람이 검토, migration:show 사용

L3 — spec 통증 (TC39 / 외부)

통증증상우회
데코레이터 spec 정체TC39 데코레이터 제안은 2014년 시작 → 12년째 stage 2~3. TypeScript가 2023년 새 stage 3 데코레이터를 도입했지만 TypeORM은 *legacy experimentalDecorators*에 묶여 있음.tsconfigexperimentalDecorators: true, emitDecoratorMetadata: true 강제.
reflect-metadata polyfill표준 진입이 불확실 — 모든 진입점에서 import "reflect-metadata"를 한 번 호출해야 함.부트스트랩 첫 줄에 import, 빌드 도구에서 자동 prepend
emitDecoratorMetadata 비용모든 클래스에 type metadata 객체를 emit — 번들 크기/컴파일 시간 증가.새 데코레이터 spec이 정착될 때까지 감수하거나, 다른 ORM

What-if — 통증을 분류하지 않으면

  • L1을 L2로 오해하면: 옮기지 않아도 될 통증에 6~9개월을 쓴다 — findOne 사고는 ESLint 룰 한 줄로 막을 수 있는 통증이다.
  • L2를 L1으로 오해하면: 영원히 못 푸는 통증영원히 패치하려 한다 — ESM 지원을 사용자가 풀 수는 없다.
  • L3를 모르면: 모든 데코레이터 ORM이 같은 위험을 공유한다는 사실을 모른다 — Sequelize-typescript, MikroORM도 같은 spec 위에 서 있다.

Insight — 이슈 트래커의 패턴

“미해결 이슈의 모양이 ORM의 건강을 보여준다”

2024~2025년 시점 TypeORM GitHub의 이슈 트래커는 3,000개 이상 열려 있고, 수년 단위로 머무는 이슈가 다수 — 그 중 일부는 재현 가능한 데이터 손실 버그다. PR도 수백 개가 review 대기. 이 모양은 두 가지를 동시에 의미한다 — 광범위한 사용자 기반(이슈가 그만큼 발생)과 메인테이너 capacity 부족.

비교 대상: Prisma의 미해결 이슈도 비슷한 수지만 평균 응답 시간주간 릴리스가 다르다. 결국 코드 품질의 절대값이 아니라 움직이는 속도가 신뢰의 기준이 된다.

한 가지 더 흥미로운 사실: TypeORM의 가장 큰 기여자NestJS 코어 팀이 아니다 — NestJS는 TypeORM을 사용하지만 유지보수에 직접 참여하지 않는다. 두 프로젝트의 운명은 기술적으로는 결합되어 있지만 조직적으로는 분리되어 있다 — 이 사실이 NestJS 사용자의 이주 의사결정을 복잡하게 만든다.


한 단락 요약

TypeORM의 통증은 *코드(팀이 패치) · 생태계(메인테이너) · spec(TC39)*의 세 층에 분포한다. L1만으로 이주를 결심하면 과잉 반응이고, L2~L3가 임계점을 넘으면 이주가 합리적이다. 분류 자체가 의사결정을 명료하게 만든다. 이 챕터를 끝내면 “TypeORM은 별로다”라는 모호한 판단 대신 “우리 통증은 L1인가 L2~L3인가, 풀 사람이 누구인가” 라는 작동 가능한 질문을 던지게 된다.