HooneyLog
© 2026 Seunghoon Shin. All rights reserved.
모든 게시글
Backend
2026. 3. 28.•
10

Prisma ORM 완벽 가이드: 스키마 설계부터 실무 쿼리까지 총정리

Seunghoon Shin
작성자 Seunghoon Shin풀스택 개발자

Prisma ORM 완벽 가이드: 스키마 설계부터 실무 쿼리까지 총정리

1. 문제의 배경: 왜 다시 Prisma인가?

기존의 TypeORM이나 Sequelize 같은 ORM들은 훌륭하지만, TypeScript 환경에서 완벽한 타입 동기화를 유지하기 위해 개발자가 수동으로 엔티티(Entity)를 정의하고 관리해야 하는 번거로움이 있었습니다.

"DB 스키마는 바뀌었는데 타입 정의를 깜빡해서 런타임 에러가 난다거나", "복잡한 Join 쿼리를 짤 때 타입 추론이 깨지는 현상"은 개발 생산성을 깎아먹는 주범이었죠. Prisma는 이러한 고질적인 문제를 "Schema-First" 접근법과 "자동 코드 생성(Code Generation)"으로 완전히 해결했습니다.

2. 핵심 아키텍처: Prisma의 3대 구성 요소

Prisma는 단순한 라이브러리가 아니라 다음과 같은 시스템으로 이루어져 있습니다.

  1. Prisma Schema (schema.prisma): 데이터베이스 구조, 관계, 생성 옵션을 한곳에서 관리하는 단일 진실 공급원(Single Source of Truth)입니다.
  2. Prisma Client: 스키마를 바탕으로 자동 생성되는 타입 안전한 쿼리 빌더입니다. 내 프로젝트에 딱 맞는 메서드들이 생성됩니다.
  3. Prisma Studio: 데이터베이스 데이터를 GUI 환경에서 바로 확인하고 수정할 수 있는 관리 도구입니다. (npx prisma studio)

3. 구현: Prisma 스키마 정의하기

Prisma의 마법은 schema.prisma 파일에서 시작됩니다.

기본 모델 정의

model User { id Int @id @default(autoincrement()) email String @unique name String? role Role @default(USER) posts Post[] // 1:N 관계 (한 명의 유저는 여러 게시글을 가짐) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId Int } enum Role { USER ADMIN }

관계(Relation) 설정의 묘미

  • 1:N 관계: 위 예시처럼 Post 모델에 authorId를 두고 @relation으로 연결합니다.
  • M:N 관계: Prisma는 별도의 중간 테이블(Join Table) 없이도 암시적(Implicit) 다대다 관계를 지원합니다. 단순히 서로를 배열로 정의하면 Prisma가 알아서 중간 테이블을 관리해 줍니다.

4. 실무 쿼리 문법 (Prisma Client 사용법)

1) 데이터 조회 (Read)

가장 많이 쓰이는 조회 방식입니다.

// 단건 조회 (unique 필드로만 가능) const user = await prisma.user.findUnique({ where: { email: 'test@example.com' }, }); // 조건 조회 및 관계 데이터 포함 (Join) const posts = await prisma.post.findMany({ where: { published: true, title: { contains: 'Prisma' }, // 'Prisma'가 포함된 제목 검색 }, include: { author: true, // 작성자 정보도 함께 가져옴 (Join 효과) }, orderBy: { createdAt: 'desc' }, take: 10, // 페이지네이션 (개수 제한) });

2) 데이터 생성 및 업데이트 (Write)

Prisma의 강력한 기능 중 하나인 중첩 쓰기(Nested Writes)입니다.

// 유저를 만들면서 동시에 게시글도 하나 생성하기 const newUser = await prisma.user.create({ data: { email: 'new@user.com', name: 'Hooney', posts: { create: { title: '첫 번째 글입니다!' }, }, }, }); // Upsert (있으면 업데이트, 없으면 생성) const user = await prisma.user.upsert({ where: { email: 'test@user.com' }, update: { name: 'Updated Name' }, create: { email: 'test@user.com', name: 'New User' }, });

3) 성능 최적화: Select vs Include

  • include: 연관된 모델의 모든 컬럼을 가져옵니다.
  • select: 필요한 특정 컬럼만 골라서 가져옵니다. (네트워크 비용 절감 및 보안상 유리)
const users = await prisma.user.findMany({ select: { id: true, name: true, // 특정 관계의 개수만 가져오기 _count: { select: { posts: true } } } });

5. 트랜잭션 및 고급 기능

순차적 트랜잭션

여러 작업을 하나로 묶어 실패 시 롤백되게 합니다.

const [user, post] = await prisma.$transaction([ prisma.user.create({ data: { name: 'A' } }), prisma.post.create({ data: { title: 'B' } }), ]);

대화형 트랜잭션 (Interactive Transactions)

이전 작업의 결과를 다음 작업에서 써야 할 때 유용합니다.

await prisma.$transaction(async (tx) => { const sender = await tx.account.update({ where: { id: 1 }, data: { balance: { decrement: 100 } }, }); if (sender.balance < 0) throw new Error('잔액 부족'); await tx.account.update({ where: { id: 2 }, data: { balance: { increment: 100 } }, }); });

6. 마치며: Prisma 도입을 고민하는 분들께

Prisma는 단순히 DB를 다루는 도구를 넘어, "데이터베이스의 상태와 내 코드의 타입을 일치"시켜주는 든든한 가드레일입니다.

특히 Next.js나 NestJS 환경에서 TypeScript를 사용한다면, Prisma가 제공하는 자동 완성 기능을 한 번 맛본 뒤에는 다시는 예전으로 돌아가기 힘들 것입니다. 초기 설정에 대한 두려움을 버리고, 공식 문서와 이 가이드를 따라 차근차근 도입해 보시길 권장합니다.

데이터베이스 관리가 더 이상 고통이 아닌 즐거움이 되는 경험을 하시게 될 것입니다!

← 이전 글TypeORM vs Prisma: Supabase 환경에서의 최적의 ORM 선택과 운영 전략
다음 글 →Supabase RLS 완벽 가이드: anon key가 털려도 내 DB가 안전한 이유