기존의 TypeORM이나 Sequelize 같은 ORM들은 훌륭하지만, TypeScript 환경에서 완벽한 타입 동기화를 유지하기 위해 개발자가 수동으로 엔티티(Entity)를 정의하고 관리해야 하는 번거로움이 있었습니다.
"DB 스키마는 바뀌었는데 타입 정의를 깜빡해서 런타임 에러가 난다거나", "복잡한 Join 쿼리를 짤 때 타입 추론이 깨지는 현상"은 개발 생산성을 깎아먹는 주범이었죠. Prisma는 이러한 고질적인 문제를 "Schema-First" 접근법과 "자동 코드 생성(Code Generation)"으로 완전히 해결했습니다.
Prisma는 단순한 라이브러리가 아니라 다음과 같은 시스템으로 이루어져 있습니다.
schema.prisma): 데이터베이스 구조, 관계, 생성 옵션을 한곳에서 관리하는 단일 진실 공급원(Single Source of Truth)입니다.npx prisma studio)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 }
Post 모델에 authorId를 두고 @relation으로 연결합니다.가장 많이 쓰이는 조회 방식입니다.
// 단건 조회 (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, // 페이지네이션 (개수 제한) });
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' }, });
include: 연관된 모델의 모든 컬럼을 가져옵니다.select: 필요한 특정 컬럼만 골라서 가져옵니다. (네트워크 비용 절감 및 보안상 유리)const users = await prisma.user.findMany({ select: { id: true, name: true, // 특정 관계의 개수만 가져오기 _count: { select: { posts: true } } } });
여러 작업을 하나로 묶어 실패 시 롤백되게 합니다.
const [user, post] = await prisma.$transaction([ prisma.user.create({ data: { name: 'A' } }), prisma.post.create({ data: { title: 'B' } }), ]);
이전 작업의 결과를 다음 작업에서 써야 할 때 유용합니다.
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 } }, }); });
Prisma는 단순히 DB를 다루는 도구를 넘어, "데이터베이스의 상태와 내 코드의 타입을 일치"시켜주는 든든한 가드레일입니다.
특히 Next.js나 NestJS 환경에서 TypeScript를 사용한다면, Prisma가 제공하는 자동 완성 기능을 한 번 맛본 뒤에는 다시는 예전으로 돌아가기 힘들 것입니다. 초기 설정에 대한 두려움을 버리고, 공식 문서와 이 가이드를 따라 차근차근 도입해 보시길 권장합니다.
데이터베이스 관리가 더 이상 고통이 아닌 즐거움이 되는 경험을 하시게 될 것입니다!