Prisma를 이용한 데이터베이스 작업
Prisma는 차세대 ORM으로 NestJS와 함께 사용하면 타입 안전성과 강력한 쿼리 기능을 결합하여 효율적인 데이터베이스 작업을 수행할 수 있습니다.
Prisma 설정 및 NestJS 통합
- Prisma 설치
npm install prisma --save-dev
npm install @prisma/client
- Prisma 초기화
npx prisma init
prisma/schema.prisma
파일에 스키마 정의
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
- 마이그레이션 실행
npx prisma migrate dev --name init
- Prisma 서비스 생성 (
prisma.service.ts
)
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
app.module.ts
에 PrismaService 등록
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class AppModule {}
Prisma Client 주입 및 사용
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
// 서비스 메서드...
}
CRUD 작업 구현
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}
async getAllUsers(): Promise<User[]> {
return this.prisma.user.findMany();
}
async getUserById(id: number): Promise<User | null> {
return this.prisma.user.findUnique({
where: { id },
});
}
async updateUser(id: number, data: Prisma.UserUpdateInput): Promise<User> {
return this.prisma.user.update({
where: { id },
data,
});
}
async deleteUser(id: number): Promise<User> {
return this.prisma.user.delete({
where: { id },
});
}
}
고급 쿼리 기능
관계 쿼리
async getUserWithPosts(id: number): Promise<User | null> {
return this.prisma.user.findUnique({
where: { id },
include: { posts: true },
});
}
필터링 및 정렬
async getPublishedPosts(orderBy: 'asc' | 'desc' = 'desc'): Promise<Post[]> {
return this.prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: orderBy },
});
}
트랜잭션 관리
async createUserWithPost(userData: Prisma.UserCreateInput, postData: Prisma.PostCreateInput): Promise<User> {
return this.prisma.$transaction(async (prisma) => {
const user = await prisma.user.create({ data: userData });
await prisma.post.create({
data: {
...postData,
authorId: user.id,
},
});
return user;
});
}
중첩 쓰기 작업
async createUserWithPosts(data: Prisma.UserCreateInput & { posts: Prisma.PostCreateInput[] }): Promise<User> {
return this.prisma.user.create({
data: {
...data,
posts: {
create: data.posts,
},
},
include: { posts: true },
});
}
타입 안정성 활용
Prisma Client는 자동으로 타입을 생성하므로 이를 NestJS 서비스에서 직접 활용할 수 있습니다.
import { User, Prisma } from '@prisma/client';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({ data });
}
}
성능 최적화 전략
- 선택적 필드 조회
this.prisma.user.findMany({
select: { id: true, name: true },
});
- 페이지네이션
async getUsersWithPagination(page: number, pageSize: number): Promise<User[]> {
return this.prisma.user.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
});
}
- 인덱스 활용
Prisma 스키마에서 인덱스 정의
model User {
id Int @id @default(autoincrement())
email String @unique
name String
@@index([name])
}
- 벌크 연산
async publishAllPosts(): Promise<Prisma.BatchPayload> {
return this.prisma.post.updateMany({
where: { published: false },
data: { published: true },
});
}
Best Practices와 주의사항
- 환경 변수 관리 : 데이터베이스 연결 정보는 환경 변수로 관리하세요.
- 마이그레이션 관리 : 스키마 변경사항을 마이그레이션으로 관리하고, 버전 관리 시스템에 포함시키세요.
- 타입 활용 : Prisma가 생성한 타입을 최대한 활용하여 타입 안정성을 확보하세요.
- N+1 문제 주의 : 관계 데이터를 조회할 때
include
를 적절히 사용하여 N+1 문제를 방지하세요. - 트랜잭션 활용 : 여러 연산이 원자적으로 실행되어야 할 때는 트랜잭션을 사용하세요.
- 로깅 설정 : 개발 환경에서는 쿼리 로깅을 활성화하여 디버깅을 용이하게 하세요.
- 비동기 처리 : Prisma 연산은 비동기이므로,
async/await
를 적절히 사용하세요. - 에러 처리 : Prisma 연산 시 발생할 수 있는 에러를 적절히 처리하세요.
- 코드 생성 자동화 :
prisma generate
명령을 빌드 프로세스에 포함시켜 타입 정의를 최신 상태로 유지하세요. - 테스트 데이터베이스 : 테스트용 별도 데이터베이스를 사용하여 테스트의 독립성을 보장하세요.
Prisma의 스키마 정의 방식은 데이터베이스 구조를 명확하게 표현하며, 자동 생성되는 타입은 TypeScript의 장점을 극대화합니다.
Prisma Client의 직관적인 API는 복잡한 쿼리도 쉽게 작성할 수 있게 해주며, NestJS의 의존성 주입 시스템과 잘 통합되어 테스트 용이성을 제공합니다.
트랜잭션 지원과 중첩 쓰기 기능은 복잡한 데이터 조작 작업을 단순화합니다.