icon

Prisma를 이용한 데이터베이스 작업


 Prisma는 차세대 ORM으로 NestJS와 함께 사용하면 타입 안전성과 강력한 쿼리 기능을 결합하여 효율적인 데이터베이스 작업을 수행할 수 있습니다.

Prisma 설정 및 NestJS 통합

  1. Prisma 설치
npm install prisma --save-dev
npm install @prisma/client
  1. Prisma 초기화
npx prisma init
  1. 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
}
  1. 마이그레이션 실행
npx prisma migrate dev --name init
  1. 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();
  }
}
  1. app.module.ts에 PrismaService 등록
app.module.ts
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 });
  }
}

성능 최적화 전략

  1. 선택적 필드 조회
this.prisma.user.findMany({
  select: { id: true, name: true },
});
  1. 페이지네이션
async getUsersWithPagination(page: number, pageSize: number): Promise<User[]> {
  return this.prisma.user.findMany({
    skip: (page - 1) * pageSize,
    take: pageSize,
  });
}
  1. 인덱스 활용

 Prisma 스키마에서 인덱스 정의

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  name  String
 
  @@index([name])
}
  1. 벌크 연산
async publishAllPosts(): Promise<Prisma.BatchPayload> {
  return this.prisma.post.updateMany({
    where: { published: false },
    data: { published: true },
  });
}

Best Practices와 주의사항

  1. 환경 변수 관리 : 데이터베이스 연결 정보는 환경 변수로 관리하세요.
  2. 마이그레이션 관리 : 스키마 변경사항을 마이그레이션으로 관리하고, 버전 관리 시스템에 포함시키세요.
  3. 타입 활용 : Prisma가 생성한 타입을 최대한 활용하여 타입 안정성을 확보하세요.
  4. N+1 문제 주의 : 관계 데이터를 조회할 때 include를 적절히 사용하여 N+1 문제를 방지하세요.
  5. 트랜잭션 활용 : 여러 연산이 원자적으로 실행되어야 할 때는 트랜잭션을 사용하세요.
  6. 로깅 설정 : 개발 환경에서는 쿼리 로깅을 활성화하여 디버깅을 용이하게 하세요.
  7. 비동기 처리 : Prisma 연산은 비동기이므로, async/await를 적절히 사용하세요.
  8. 에러 처리 : Prisma 연산 시 발생할 수 있는 에러를 적절히 처리하세요.
  9. 코드 생성 자동화 : prisma generate 명령을 빌드 프로세스에 포함시켜 타입 정의를 최신 상태로 유지하세요.
  10. 테스트 데이터베이스 : 테스트용 별도 데이터베이스를 사용하여 테스트의 독립성을 보장하세요.

 Prisma의 스키마 정의 방식은 데이터베이스 구조를 명확하게 표현하며, 자동 생성되는 타입은 TypeScript의 장점을 극대화합니다.

 Prisma Client의 직관적인 API는 복잡한 쿼리도 쉽게 작성할 수 있게 해주며, NestJS의 의존성 주입 시스템과 잘 통합되어 테스트 용이성을 제공합니다.

 트랜잭션 지원과 중첩 쓰기 기능은 복잡한 데이터 조작 작업을 단순화합니다.