Prisma 완벽 가이드: 차세대 TypeScript ORM

Prisma 완벽 가이드: 차세대 TypeScript ORM

이 글의 핵심

Prisma는 타입 안전하고 직관적인 차세대 ORM으로 Prisma Schema로 DB를 정의하고 자동 생성된 타입 안전 클라이언트로 쿼리합니다. 마이그레이션, Prisma Studio GUI, 강력한 관계 쿼리로 생산성이 높습니다.

Prisma란?

Prisma는 차세대 TypeScript ORM(Object-Relational Mapping)으로, 타입 안전성과 개발자 경험을 극대화한 데이터베이스 툴킷입니다.

핵심 구성 요소

  1. Prisma Schema

    • 선언적 데이터 모델
    • 단일 진실 공급원
    • 마이그레이션 생성
  2. Prisma Client

    • 자동 생성 쿼리 빌더
    • 완벽한 타입 안전성
    • 자동완성 지원
  3. Prisma Migrate

    • 스키마 마이그레이션
    • 버전 관리
    • 롤백 지원
  4. Prisma Studio

    • GUI 데이터 브라우저
    • 데이터 편집
    • 시각적 탐색

TypeORM vs Sequelize vs Prisma 비교

항목TypeORMSequelizePrisma
언어TypeScriptJavaScript/TSTypeScript
스키마 정의데코레이터 클래스모델 클래스Schema 파일
타입 안전성좋음보통매우 좋음
마이그레이션CLICLIMigrate CLI
Raw SQL지원지원지원
관계 쿼리Eager/LazyIncludeInclude
N+1 해결수동수동자동
GUIPrisma Studio
학습 곡선중간중간낮음

프로젝트 설정

# Prisma 설치
npm install -D prisma
npm install @prisma/client

# Prisma 초기화
npx prisma init

# PostgreSQL 사용 시
npx prisma init --datasource-provider postgresql

# MySQL 사용 시
npx prisma init --datasource-provider mysql

디렉터리 구조

my-project/
├── prisma/
│   ├── schema.prisma     # 스키마 정의
│   └── migrations/       # 마이그레이션 파일
├── src/
│   └── index.ts
├── .env                  # DATABASE_URL
└── package.json

Prisma Schema

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  role      Role     @default(USER)
  posts     Post[]
  profile   Profile?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([email])
  @@map("users")  // 테이블명 지정
}

model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  userId Int     @unique
  user   User    @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  tags      Tag[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([authorId])
  @@index([published])
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[]
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

마이그레이션

# 마이그레이션 생성 및 적용
npx prisma migrate dev --name init

# 프로덕션 적용
npx prisma migrate deploy

# 마이그레이션 상태 확인
npx prisma migrate status

# 마이그레이션 리셋 (개발 전용)
npx prisma migrate reset

# Prisma Client 재생성
npx prisma generate

Prisma Client 사용

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Create
const user = await prisma.user.create({
  data: {
    email: 'hong@example.com',
    name: '홍길동',
    posts: {
      create: [
        { title: '첫 번째 글' },
        { title: '두 번째 글' }
      ]
    }
  }
});

// Read
const users = await prisma.user.findMany();
const user = await prisma.user.findUnique({
  where: { email: 'hong@example.com' }
});

// Include 관계
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: true,
    profile: true
  }
});

// Update
const user = await prisma.user.update({
  where: { id: 1 },
  data: { name: '김철수' }
});

// Delete
await prisma.user.delete({
  where: { id: 1 }
});

고급 쿼리

필터링

// AND 조건
const users = await prisma.user.findMany({
  where: {
    AND: [
      { age: { gte: 20 } },
      { role: 'USER' }
    ]
  }
});

// OR 조건
const users = await prisma.user.findMany({
  where: {
    OR: [
      { email: { contains: 'gmail.com' } },
      { email: { contains: 'naver.com' } }
    ]
  }
});

// NOT 조건
const users = await prisma.user.findMany({
  where: {
    NOT: { role: 'ADMIN' }
  }
});

관계 필터링

// 포스트가 있는 사용자만
const users = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        published: true
      }
    }
  }
});

// 특정 태그가 있는 포스트
const posts = await prisma.post.findMany({
  where: {
    tags: {
      some: {
        name: 'typescript'
      }
    }
  },
  include: {
    author: true,
    tags: true
  }
});

집계

// 카운트
const userCount = await prisma.user.count();
const activeUsers = await prisma.user.count({
  where: { isActive: true }
});

// 그룹화 집계
const stats = await prisma.user.groupBy({
  by: ['role'],
  _count: { id: true },
  _avg: { age: true }
});

트랜잭션

// 연속 트랜잭션
const [user, post] = await prisma.$transaction([
  prisma.user.create({ data: { email: 'user@example.com' } }),
  prisma.post.create({ data: { title: 'Hello' } })
]);

// 대화형 트랜잭션
await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { email: 'user@example.com' }
  });
  
  await tx.post.create({
    data: {
      title: 'Hello',
      authorId: user.id
    }
  });
  
  // 에러 발생 시 자동 롤백
});

성능 최적화

// Select로 필요한 필드만
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    email: true
  }
});

// 페이지네이션
const users = await prisma.user.findMany({
  skip: 10,
  take: 10
});

// 커서 기반 페이지네이션
const users = await prisma.user.findMany({
  take: 10,
  cursor: {
    id: lastUserId
  },
  skip: 1
});

Prisma는 현대적이고 타입 안전한 ORM입니다. 직관적인 API와 강력한 타입 시스템으로 데이터베이스 작업을 즐겁게 만듭니다.