본문 바로가기

TypeORM vs Prisma: Node.js ORM 심층 비교

민이(MInE) 2024. 11. 11.
반응형

목차

  1. 개요
  2. 기본 개념 비교
  3. 스키마 정의 방식
  4. 타입 안정성
  5. 쿼리 작성 방식
  6. 성능 비교
  7. 실제 사용 예제
  8. 장단점 분석

개요

TypeORM과 Prisma는 Node.js 생태계에서 가장 인기 있는 ORM(Object-Relational Mapping) 도구입니다. 각각의 특징과 차이점을 살펴보고, 프로젝트 상황에 맞는 선택을 할 수 있도록 도와드리겠습니다.

기본 개념 비교

TypeORM

  • 전통적인 ORM 패턴 따름
  • Active Record 또는 Data Mapper 패턴 선택 가능
  • 데코레이터 기반의 스키마 정의
  • JavaScript/TypeScript로 작성된 코드베이스

Prisma

  • 새로운 접근 방식의 차세대 ORM
  • 독자적인 스키마 정의 언어(Prisma Schema)
  • 강력한 타입 안정성
  • 자동 생성되는 타입-세이프 클라이언트

스키마 정의 방식

TypeORM 엔티티 정의

// TypeORM
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;

  @Column()
  name: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

Prisma 스키마 정의

// Prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String
  posts Post[]
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}

타입 안정성

TypeORM

// TypeORM - 타입 추론이 때때로 불완전
const users = await userRepository.find({
  where: {
    email: 'test@example.com',
    // 오타나 잘못된 필드 이름이 런타임에 발견될 수 있음
    namee: 'John' // ❌ 컴파일 시점에 오류 감지 어려움
  }
});

Prisma

// Prisma - 완벽한 타입 안정성
const users = await prisma.user.findMany({
  where: {
    email: 'test@example.com',
    namee: 'John' // ✅ 컴파일 시점에 오류 감지
  }
});

쿼리 작성 방식

TypeORM 쿼리

// TypeORM
// 복잡한 쿼리
const posts = await postRepository
  .createQueryBuilder('post')
  .leftJoinAndSelect('post.author', 'author')
  .where('author.email = :email', { email: 'test@example.com' })
  .andWhere('post.createdAt > :date', { date: new Date('2024-01-01') })
  .orderBy('post.createdAt', 'DESC')
  .take(10)
  .getMany();

// 기본 CRUD
const user = await userRepository.save({
  email: 'test@example.com',
  name: 'John Doe'
});

Prisma 쿼리

// Prisma
// 복잡한 쿼리
const posts = await prisma.post.findMany({
  where: {
    author: {
      email: 'test@example.com'
    },
    createdAt: {
      gt: new Date('2024-01-01')
    }
  },
  include: {
    author: true
  },
  orderBy: {
    createdAt: 'desc'
  },
  take: 10
});

// 기본 CRUD
const user = await prisma.user.create({
  data: {
    email: 'test@example.com',
    name: 'John Doe'
  }
});

성능 비교

메모리 사용량

// TypeORM
// 메모리에 전체 엔티티를 로드
const users = await userRepository.find();

// Prisma
// 필요한 필드만 선택적으로 로드
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true
  }
});

쿼리 최적화

// TypeORM - N+1 문제 발생 가능
const posts = await postRepository.find();
for (const post of posts) {
  const author = await post.author; // N+1 쿼리 발생
}

// Prisma - 자동 최적화
const posts = await prisma.post.findMany({
  include: {
    author: true // 단일 쿼리로 최적화
  }
});

실제 사용 예제

TypeORM으로 구현한 게시판

// TypeORM 예제
@Entity()
export class Board {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Column()
  content: string;

  @ManyToOne(() => User, user => user.boards)
  author: User;

  @CreateDateColumn()
  createdAt: Date;
}

@Injectable()
export class BoardService {
  constructor(
    @InjectRepository(Board)
    private boardRepository: Repository<Board>
  ) {}

  async createBoard(data: CreateBoardDto, author: User): Promise<Board> {
    const board = this.boardRepository.create({
      ...data,
      author
    });
    return await this.boardRepository.save(board);
  }
}

Prisma로 구현한 게시판

// schema.prisma
model Board {
  id        String   @id @default(uuid())
  title     String
  content   String
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
}

// BoardService
@Injectable()
export class BoardService {
  constructor(private prisma: PrismaService) {}

  async createBoard(data: CreateBoardDto, authorId: string): Promise<Board> {
    return await this.prisma.board.create({
      data: {
        ...data,
        author: {
          connect: { id: authorId }
        }
      }
    });
  }
}

장단점 분석

TypeORM 장점

  1. 유연성
    • 다양한 데이터베이스 지원
    • Active Record/Data Mapper 패턴 선택 가능
    • 복잡한 쿼리 빌더 제공
  2. 생태계
    • 오랜 기간 검증된 안정성
    • 풍부한 레퍼런스와 커뮤니티
    • NestJS와의 긴밀한 통합
  3. 마이그레이션
    • 자동 마이그레이션 생성
    • 세밀한 마이그레이션 제어 가능

TypeORM 단점

  1. 타입 안정성
    • 불완전한 타입 추론
    • 런타임 오류 가능성
  2. 성능
    • N+1 쿼리 문제
    • 메모리 사용량 이슈
  3. 복잡성
    • 상대적으로 가파른 학습 곡선
    • 복잡한 관계 설정

Prisma 장점

  1. 타입 안정성
    • 완벽한 타입 추론
    • 컴파일 타임 오류 검출
  2. 개발자 경험
    • 직관적인 API
    • 우수한 IDE 지원
    • 자동 완성 지원
  3. 성능
    • 자동 쿼리 최적화
    • 효율적인 데이터 로딩

Prisma 단점

  1. 유연성
    • 제한된 데이터베이스 지원
    • 복잡한 쿼리의 제한적 지원
  2. 학습 곡선
    • 새로운 스키마 언어 학습 필요
    • 독자적인 접근 방식
  3. 마이그레이션
    • 제한적인 마이그레이션 제어
    • 복잡한 마이그레이션 시나리오 처리의 어려움

선택 가이드

TypeORM이 적합한 경우

  • 복잡한 레거시 데이터베이스와의 통합
  • 세밀한 쿼리 제어가 필요한 경우
  • Active Record 패턴 선호
  • 다양한 데이터베이스 지원 필요

Prisma가 적합한 경우

  • 새로운 프로젝트 시작
  • 타입 안정성 중시
  • 빠른 개발 속도 필요
  • 단순하고 명확한 API 선호

마치며

두 ORM은 각각의 장단점이 있으며, 프로젝트의 요구사항과 팀의 선호도에 따라 선택할 수 있습니다. TypeORM은 전통적이고 유연한 접근 방식을, Prisma는 현대적이고 타입 안전한 접근 방식을 제공합니다.

참고 자료

https://octoping.tistory.com/37

 

[번역] TypeORM vs Prisma

들어가기 앞서 해당 글은 Prisma의 공식 문서인 해당 글을 번역하였습니다. https://www.prisma.io/docs/concepts/more/comparisons/prisma-and-typeorm Prisma vs TypeORM Learn how Prisma compares to TypeORM. www.prisma.io TypeORM vs Prism

octoping.tistory.com

 

반응형

댓글