🗄️ 데이터베이스

Prisma

Next-generation Node.js and TypeScript ORM

차세대 Node.js 및 TypeScript ORM입니다. 타입 안전 쿼리, 자동 마이그레이션, 직관적인 데이터 모델링을 제공하여 개발자 경험(DX)을 극대화합니다.

📖 상세 설명

Prisma는 기존 ORM(Sequelize, TypeORM 등)의 복잡성을 해결하기 위해 설계된 차세대 데이터베이스 툴킷입니다. 선언적 스키마 정의, 자동 타입 생성, 직관적인 API를 통해 데이터베이스 작업을 단순화합니다.

Prisma Schema는 데이터 모델을 정의하는 핵심 파일입니다. 여기서 모델, 관계, 데이터베이스 연결을 선언하면 Prisma가 TypeScript 타입과 쿼리 클라이언트를 자동 생성합니다. 이를 통해 컴파일 타임에 쿼리 오류를 잡아낼 수 있습니다.

Prisma Client는 자동 생성되는 타입 안전 쿼리 빌더입니다. findMany, create, update 등 직관적인 메서드와 함께 자동완성을 제공하여 SQL을 직접 작성하지 않고도 복잡한 쿼리를 구성할 수 있습니다.

Prisma Migrate는 스키마 변경을 추적하고 데이터베이스에 적용하는 마이그레이션 시스템입니다. 개발 환경에서는 prisma migrate dev로 빠르게 반복하고, 프로덕션에서는 prisma migrate deploy로 안전하게 배포합니다.

Prisma Studio는 브라우저 기반 데이터베이스 GUI로, 데이터를 시각적으로 조회하고 편집할 수 있습니다. PostgreSQL, MySQL, SQLite, MongoDB, SQL Server 등 다양한 데이터베이스를 지원합니다.

💻 코드 예제

// prisma/schema.prisma - Prisma 스키마 파일

// 데이터베이스 연결 설정
datasource db {
  provider = "postgresql"  // postgresql, mysql, sqlite, mongodb
  url      = env("DATABASE_URL")
}

// Prisma Client 생성 설정
generator client {
  provider = "prisma-client-js"
}

// 사용자 모델
model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  name      String?
  password  String
  role      Role      @default(USER)
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  // 관계: 1:N (User -> Post)
  posts     Post[]
  profile   Profile?  // 1:1 관계

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

// 게시글 모델
model Post {
  id          Int       @id @default(autoincrement())
  title       String
  content     String?
  published   Boolean   @default(false)
  authorId    Int
  createdAt   DateTime  @default(now())

  // 관계: N:1 (Post -> User)
  author      User      @relation(fields: [authorId], references: [id], onDelete: Cascade)

  // N:M 관계 (Post <-> Category)
  categories  Category[]

  @@index([authorId])
  @@map("posts")
}

// 카테고리 모델 (N:M 관계)
model Category {
  id    Int     @id @default(autoincrement())
  name  String  @unique
  posts Post[]

  @@map("categories")
}

// 프로필 모델 (1:1 관계)
model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  userId Int     @unique
  user   User    @relation(fields: [userId], references: [id])

  @@map("profiles")
}

// Enum 정의
enum Role {
  USER
  ADMIN
  MODERATOR
}
// Prisma Client 사용 예제 - TypeScript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// ============================================
// CRUD 기본 작업
// ============================================

// 사용자 생성 (Create)
async function createUser() {
  const user = await prisma.user.create({
    data: {
      email: 'alice@example.com',
      name: 'Alice',
      password: 'hashedPassword123',
      // 관계 데이터도 함께 생성
      posts: {
        create: [
          { title: '첫 번째 글', content: '안녕하세요!' },
          { title: '두 번째 글', published: true }
        ]
      },
      profile: {
        create: { bio: '개발자입니다.' }
      }
    },
    // 관계 데이터 포함하여 반환
    include: {
      posts: true,
      profile: true
    }
  })
  return user
}

// 조회 (Read) - 다양한 필터링
async function getUsers() {
  // 조건부 조회
  const users = await prisma.user.findMany({
    where: {
      OR: [
        { email: { contains: '@gmail.com' } },
        { role: 'ADMIN' }
      ],
      posts: {
        some: { published: true }  // 게시된 글이 있는 사용자
      }
    },
    select: {
      id: true,
      name: true,
      email: true,
      _count: {
        select: { posts: true }  // 게시글 수
      }
    },
    orderBy: { createdAt: 'desc' },
    take: 10,  // LIMIT
    skip: 0    // OFFSET
  })
  return users
}

// 단일 조회 (없으면 에러)
async function getUserOrThrow(id: number) {
  const user = await prisma.user.findUniqueOrThrow({
    where: { id },
    include: { posts: { where: { published: true } } }
  })
  return user
}

// 업데이트 (Update)
async function updateUser(id: number) {
  const user = await prisma.user.update({
    where: { id },
    data: {
      name: 'Alice Kim',
      // 관계 데이터 수정
      posts: {
        updateMany: {
          where: { published: false },
          data: { published: true }
        }
      }
    }
  })
  return user
}

// 삭제 (Delete)
async function deleteUser(id: number) {
  // Cascade 설정으로 관련 posts도 삭제됨
  await prisma.user.delete({
    where: { id }
  })
}

// ============================================
// 트랜잭션
// ============================================

async function transferPoints(fromId: number, toId: number, amount: number) {
  // Interactive Transaction
  const result = await prisma.$transaction(async (tx) => {
    // 1. 포인트 차감
    const sender = await tx.user.update({
      where: { id: fromId },
      data: { points: { decrement: amount } }
    })

    if (sender.points < 0) {
      throw new Error('포인트가 부족합니다')
    }

    // 2. 포인트 증가
    const receiver = await tx.user.update({
      where: { id: toId },
      data: { points: { increment: amount } }
    })

    return { sender, receiver }
  })

  return result
}

// ============================================
// Raw SQL (필요시)
// ============================================

async function rawQuery() {
  // 타입 안전 Raw 쿼리
  const users = await prisma.$queryRaw`
    SELECT * FROM users
    WHERE email LIKE ${'%@gmail.com'}
    LIMIT 10
  `
  return users
}

// 연결 종료 (앱 종료 시)
process.on('beforeExit', async () => {
  await prisma.$disconnect()
})
# Prisma CLI 명령어 모음

# ============================================
# 설치 및 초기화
# ============================================

# Prisma 설치
npm install prisma --save-dev
npm install @prisma/client

# 프로젝트 초기화 (schema.prisma 생성)
npx prisma init

# 특정 DB로 초기화
npx prisma init --datasource-provider postgresql


# ============================================
# 마이그레이션 (개발 환경)
# ============================================

# 스키마 변경 후 마이그레이션 생성 및 적용
npx prisma migrate dev --name init
npx prisma migrate dev --name add_user_role

# 마이그레이션만 생성 (적용 안함)
npx prisma migrate dev --create-only

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


# ============================================
# 마이그레이션 (프로덕션)
# ============================================

# 프로덕션 배포 (CI/CD에서 실행)
npx prisma migrate deploy

# 마이그레이션 리셋 (주의: 데이터 삭제!)
npx prisma migrate reset


# ============================================
# Prisma Client 생성
# ============================================

# 스키마 기반 Client 재생성
npx prisma generate

# 스키마 변경 시 자동 재생성 (개발용)
npx prisma generate --watch


# ============================================
# 데이터베이스 작업
# ============================================

# DB 스키마를 Prisma 스키마로 가져오기 (기존 DB)
npx prisma db pull

# Prisma 스키마를 DB에 직접 적용 (마이그레이션 없이)
npx prisma db push

# 시드 데이터 실행
npx prisma db seed


# ============================================
# Prisma Studio (GUI)
# ============================================

# 브라우저에서 데이터 관리
npx prisma studio

# 특정 포트로 실행
npx prisma studio --port 5555


# ============================================
# 기타 유용한 명령어
# ============================================

# 스키마 포맷팅
npx prisma format

# 스키마 유효성 검사
npx prisma validate

# Prisma 버전 확인
npx prisma version

🗣️ 실무에서 이렇게 말하세요

💬 기술 스택 선정 회의에서
"ORM은 Prisma로 가죠. TypeScript 타입이 자동 생성되니까 런타임 에러가 확 줄어요. 스키마 파일 하나로 모델, 타입, 마이그레이션이 다 관리되고요. Sequelize처럼 모델 파일 여러 개 만들 필요가 없어서 유지보수도 편합니다."
💬 코드 리뷰에서
"이 쿼리에서 N+1 문제 생길 것 같아요. findMany 할 때 include로 관계 데이터를 한 번에 가져오세요. Prisma가 자동으로 JOIN 쿼리로 최적화해줍니다. 아니면 select로 필요한 필드만 가져와서 오버페칭도 방지하세요."
💬 배포 파이프라인 논의에서
"CI/CD에 prisma migrate deploy 단계 추가했어요. 마이그레이션이 실패하면 배포 중단되도록요. 개발 환경에서는 migrate dev로 자유롭게 변경하고, 프로덕션에선 반드시 migrate deploy로만 적용해야 안전합니다."

⚠️ 주의사항 & 베스트 프랙티스

N+1 쿼리 문제

반복문 안에서 개별 쿼리 실행 금지. include나 select로 관계 데이터를 미리 로드하거나, findMany로 한 번에 조회하세요.

PrismaClient 인스턴스 관리

매 요청마다 new PrismaClient() 금지. 싱글톤으로 관리하거나, Next.js에서는 global 객체에 캐싱하세요. 커넥션 풀 고갈됩니다.

마이그레이션 순서 변경 금지

생성된 마이그레이션 파일의 순서나 내용을 수동으로 변경하면 안 됩니다. 문제 시 새 마이그레이션으로 해결하세요.

Prisma 베스트 프랙티스

스키마 변경 시 generate 실행, 트랜잭션은 $transaction 사용, 민감 정보는 env()로 관리, Prisma Studio로 데이터 확인.

🔗 관련 용어

📚 더 배우기