Prisma로의 이주

이 문서가 답하는 질문: 어떤 팀이, , TypeORM에서 Prisma로 옮기는가 — 그리고 그 길의 단계는 어떻게 생겼는가? 한 줄 답 (Pyramid Top): “이주의 동기는 거의 항상 같다 — 타입 정확도 + 마이그레이션 신뢰도. 길은 strangler fig다: 한 번에 다 갈아엎는 팀은 거의 없다.”


한 문장 답 (Pyramid Top)

Prisma로의 이주는 기능 추가가 아니라 고통의 제거다 — findOne deprecated 사고, ESM 지원 부재, 데코레이터 spec 정체, 부분적 타입 정확도 같은 누적된 작은 통증이 한계에 도달한 팀이 strangler fig 패턴으로 두 ORM을 한동안 공존시키며 모듈 단위로 옮긴다 — 한 번의 빅뱅 마이그레이션은 사실상 일어나지 않는다.


챕터 지도


Why — 왜 옮기는가

이주를 시작하는 팀들의 공통된 4가지 동기가 있다.

#동기TypeORM의 한계Prisma의 해결
1타입 정확도findOne()의 반환 타입이 전체 엔티티select/relations로 선택한 필드만 좁혀지지 않음스키마 → PrismaClient 코드젠으로 정확한 반환 타입
2마이그레이션 신뢰도migration:generate누락하는 diff가 있음 (특히 인덱스, 제약, enum 변경)prisma migrate dev섀도우 DB로 diff를 검증
3ESM 지원TypeORM은 CJS 기반, ESM 환경에서 typeorm-ts-node-commonjs 같은 우회 필요Prisma는 ESM 1급 시민
4메인테이너 활동2020~2022년 정체기, 큰 PR이 수개월 머무름Prisma 팀의 주간 릴리스 케이던스

한 가지 동기로 옮기는 팀은 드물다 — 네 가지 중 셋 이상이 누적되면 결심이 굳는다.


How — 어떻게 옮기는가 (strangler fig 패턴)

빅뱅 마이그레이션은 거의 일어나지 않는다. 실제로 통하는 길은 4단계다.

  1. schema 추출prisma db pull현재 DB에서 schema.prisma 생성. TypeORM 엔티티가 아니라 DB가 진실의 출발점.
  2. 공존 — 두 클라이언트를 동시에 부트하되, 마이그레이션 권한은 한쪽만 갖는다. 이 시기에 반드시 TypeORM synchronize: false.
  3. 모듈 이주주변 모듈(읽기 위주, 트랜잭션 단순)부터 옮긴다. 코어 도메인(주문·결제 같은 트랜잭션 중심)은 마지막.
  4. 정리 — 모든 호출자가 Prisma로 옮겨가면 TypeORM과 migrations 테이블을 제거하고, 마이그레이션 주인을 Prisma만으로 둔다.

What — 구체 사양 + 코드

1단계: schema 추출

# 현재 DB에서 Prisma schema 자동 생성
npx prisma db pull --schema=./prisma/schema.prisma
npx prisma generate

2단계: 공존 (한 DataSource, 두 클라이언트)

// db.ts — 동일 DB를 두 ORM이 동시에 접근
import { DataSource } from "typeorm";
import { PrismaClient } from "@prisma/client";
 
export const orm = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  synchronize: false,   // 핵심 — 자동 동기화 OFF
  migrationsRun: false, // 핵심 — 마이그레이션은 Prisma만
  entities: [/* 기존 엔티티 */],
});
 
export const prisma = new PrismaClient();

3단계: 모듈별 이주 (옛 코드 → 새 코드)

// before — TypeORM
const user = await userRepo.findOne({
  where: { id },
  relations: { profile: true },
});
//   ^^^^ User & { profile: Profile } 같은 *부정확한* 타입
 
// after — Prisma
const user = await prisma.user.findUnique({
  where: { id },
  include: { profile: true },
});
//   ^^^^ Prisma가 추론한 *정확한* 타입

4단계: 정리 체크리스트

항목확인
모든 service/repository에서 TypeORM import 제거
migrations/ 폴더 → prisma/migrations/로 이주 완료
typeorm / @nestjs/typeorm 의존성 제거
CI에서 typeorm migration:run 호출 제거
운영 DB의 migrations 테이블 수동 삭제 (선택)⚠️

What-if — 잘못 옮기면

  • 공존 기간에 두 클라이언트로 같은 트랜잭션을 묶으려고 하면: 실패한다. 두 클라이언트는 서로의 트랜잭션을 모른다 — 같은 작업 단위는 한 클라이언트로 통일해야 한다.
  • 코어 도메인을 먼저 옮기면: 트랜잭션·캐스케이드의 의미가 살짝 달라져 통합 테스트가 무더기로 깨진다 — 주변부터 시작해 학습 곡선을 흡수해야 한다.
  • synchronize: true를 켠 채로 공존시키면: TypeORM이 Prisma가 만든 컬럼을 모르겠다며 drop한다 — 비가역 데이터 손실. synchronize: false공존 1일차의 절대 조건.
  • schema.prisma를 수동으로 시작하면: TypeORM이 생성한 암묵적 인덱스·시퀀스가 누락된다 — 반드시 prisma db pullDB에서 추출.

Insight — 실제 회사들의 이주 패턴

“이주는 동기가 아니라 임계점에서 시작된다”

공개된 블로그 포스트 기준 — Strapi(2022년 v4에서 Bookshelf → Knex 이주, ORM 자체 추상화), Medusa(2024년 MikroORM으로 전환) 같은 대형 오픈소스 팀은 모두 strangler fig를 따랐다. 빅뱅이 일어났다고 보고된 사례는 없다.

공통점은 임계점의 모양이다 — 마이그레이션 사고 1건 + 타입 미스로 인한 프로덕션 버그 1건이 분기 안에 겹치면, 그 분기 retro에서 “다른 ORM을 보자”가 나온다. 그 다음 분기에 기술 spike로 schema 추출과 공존 부트를 만든다. 그 다음 두 분기에 모듈을 옮긴다. 총 6~9개월이 표준 일정이다.

더 흥미로운 사실: 옮긴 팀의 일부는 다시 Drizzle로 옮겼다. ORM 선택은 영구 결혼이 아니라 5년 단위 갱신 계약에 가깝다.


한 단락 요약

Prisma로의 이주는 타입 정확도 + 마이그레이션 신뢰도 + ESM + 메인테이너 활동의 누적된 통증이 임계점을 넘었을 때 strangler fig 패턴으로 진행된다 — schema는 DB에서 추출, 두 클라이언트는 한 DataSource 위에서 공존, 이주는 주변 → 코어, 정리는 마이그레이션 주인 일원화. 빅뱅은 일어나지 않고, 6~9개월의 점진 일정이 표준. 다음 문서(06-known-pain-points)는 이 결정을 촉발하는 구체적 통증들을 카탈로그한다.