schema.prisma 하나를 단일 진실 공급원(Single Source of Truth)으로 삼아 DB 구조와 코드 타입을 일치시킵니다.findMany, include/select), 중첩 쓰기, upsert, 순차/대화형 트랜잭션까지 실무에 필요한 문법을 한 번에 정리합니다.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가 제공하는 자동 완성을 한 번 경험한 뒤에는 이전 방식으로 돌아가기 어렵습니다. 초기 설정에 대한 부담을 내려놓고, 공식 문서와 이 가이드를 따라 차근차근 도입해 보시길 권합니다.