Prisma랑 친숙해지기

류지승·2024년 9월 16일
0

nestjs

목록 보기
3/10
post-thumbnail

Prisma란?

PrismaORM(Object Relational Mapping)의 한 종류로써, 객체와 데이터 베이스를 연결시켜주는 역할을 한다. 쉽게 설명하자면 server side에서 데이터베이스에 접근할 수 있게 해줘서 server에서 sql query를 작성할 수 있게 해주는 역할을 한다.

ORM을 사용하는 이유?

객체지향 프로그래밍 언어(OOP) 언어와 관계형 데이터베이스(RDBMS) 간의 불일치(Impedance Mismatch)를 해소하기 위함이다.
ORM(Object Relational Mapping)을 사용하는 이유는 다음과 같다.

  1. 생산성 향상 - 데이터베이스 작업을 위해 직접 SQL 쿼리 문을 작성하는 게 아닌 프로그래밍 언어의 객체를 사용하여 데이터베이스와 상호작용할 수 있게 해준다.
  2. 유지보수성 향상 - ORM을 사용하면 비즈니스 로직과 데이터베이스 액세스 로직을 더 명확하게 분리할 수 있다.
  3. 객체 지향 프로그래밍과의 일관성 - OOP와 일관된 방식으로 데이터베이스를 조작할 수 있게 해준다. 데이터베이스의 테이블이 객체의 클래스가 되고, 열은 object의 property / 행은 object의 instance가 된다.

prisma를 nestjs에 적용시키기

요새 코딩을 하면서 깨달은 사실 하나가 있다. 그것은 바로 docs 읽기이다. 진짜 document에는 모든 내용이 담겨있다. prisma를 잘 모른다고 해도 docs를 차근차근 보면서 공부하게 되면 내 것으로 바꿀 수 있다. 잘 모른다고 chatgpt 딸깍하지말고 Documents를 읽는 습관을 드리자.

nestjs documents를 보면서 prisma 설치

$ npm install prisma --save-dev
$ npx prisma

prisma 패키지를 개발 의존성으로 설치하고 npx를 이용하여 prisma를 실행시킨다. npx prisma를 하게 되면 읽기 싫은 이상한 글이 보일것이다..

하라는 대로 하면 우리는 prisma를 사용할 수 있다.

npx prisma init

먼저 이것부터 실행해보자 그럼

이렇게 보일 것이다. 하나씩 차근차근 해보자.
먼저 .envDATABASE_URL이라는 주소가 있을 것이다. DATABASE_URL = "DB종류://UserName:PW@URL:/DB?schema" 이런 형식으로 바꾸면 될 것이다.

예시 : DATABASE_URL = "mysql://root:1234@localhost:8080:/mydb?schema=public"

npx prisma generate를 실행하면, prisma/schema.prisma 파일에 정의된 데이터 모델을 기반으로 Prisma Client를 생성한다. 애플리케이션 코드에서 DB와 상호작용할 수 있는 타입 안전한 쿼리를 제공하는 역할을 한다.

model Todo {
  id    Int     @id @default(autoincrement())
  todo String  
  isDone  Boolean @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

나는 이런식으로 Todo 모델을 만들었다.

이후 prisma migrate dev를 이용해 데이터베이스의 스키마를 선언하고 업데이트한다. migrate는 객체 지향 프로그래밍 언어에서 실제 sql 쿼리를 적용시켜줄 때 사용한다. db를 보면 실제로 Table이 생성된 것을 볼 수 있다.

-- CreateTable
CREATE TABLE `Todo` (
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `todo` VARCHAR(191) NOT NULL,
    `isDone` BOOLEAN NOT NULL DEFAULT false,
    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updatedAt` DATETIME(3) NOT NULL,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

prisma를 이용하여 CRUD 구현하기

// 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();
  }
}

extends VS implements

extends

클래스가 다른 클래스를 상속할 때 사용한다. 상속 받은 클래스는 부모 클래스의 모든 메서드와 속성을 물려받아 사용할 수 있다. 다중 상속은 허용하지 않으며, 단일 상속만 가능하다.

implements

implement 같은 경우 클래스 내에 interface를 구현할 때 사용된다. 클래스 내에는 inteface로 선언된 property와 method들을 강제로 구현하게 해준다. 다중 구현이 가능하며, 구현 코드를 가지는 게 아닌 형식을 정의하는 데 사용된다.

특징extendsimplements
목적클래스를 상속받아 부모 클래스의 기능을 재사용인터페이스의 규칙을 구현하여 일관된 클래스 구조 유지
사용 대상클래스인터페이스
상속 및 구현 개수단일 상속 (한 번에 하나의 클래스만 상속 가능)다중 구현 (여러 인터페이스를 동시에 구현 가능)
상속된 요소부모 클래스의 속성과 메서드 모두 상속인터페이스의 메서드 시그니처만 구현 필요
코드 재사용성부모 클래스의 코드를 재사용하여 개발 속도 향상클래스가 일관된 인터페이스 구조를 따르도록 강제
실제 구현 여부부모 클래스의 실제 구현 코드 포함구현 코드 없음, 인터페이스의 규칙만 정의

위 코드로 예를 들면, PrismaService라는 클래스를 선언하는데, prismaClient 클래스에서 property와 method를 상속 받고, ìnterface OnModuleInit이라는 타입을 PrismaService에 강제 할당하게 한다.

prisma.service.ts는 server에서 DB를 접근할 수 있게 해주는 로직이다.

해당 모듈에서 PrismaService를 사용하게 하기 위해서는 모듈에서 provider에서 의존성 주입을 처리해줘야 한다.

import { Module } from '@nestjs/common';
import { TodosService } from './todos.service';
import { TodosController } from './todos.controller';
import { PrismaService } from 'src/prisma.service';

@Module({
  controllers: [TodosController],
  // 실제 DB에 접근할 수 있도록 PrismaService를 providers에 주입했다. 
  providers: [TodosService, PrismaService],
})
export class TodosModule {}

Prisma POST Method

DB에 접근하기 위해 먼저 constructor에 prismaService를 선언해준다. 이후 우리가 node에서 사용했던 async await를 활용하여 코드를 처리한다. postman과 mysql을 확인해보니 데이터 요청이 잘 들어온다.

// create
// tabwidth가 2인게 너무나도 불편해서 4로 변경함.
async create(createTodoDto: CreateTodoDto): Promise<Todo | null> {
	return await this.prismaService.todo.create({
    	data: {
                todo: createTodoDto.todo,
                isDone: createTodoDto.isDone,
            },
        });
    }

id / createdAt / updatedAt는 자동생성된다.
todo와 isDonereq body에 넘겨주는 input data에서 뽑아내면 된다.

Prisma GET Method

정답은 항상 Documents에 있다. 또한 GET Method 또한 docs를 참고하여 코드를 작성하였다. get 요청을 처리하는 방법은 크게 3가지 방법이 있다. findMany(), findUnique(), findFirst()

findMany - 해당 table에 있는 모든 데이터를 들고온다. SELECT * FROM TABLE
findUnique - 해당 table에 있는 모든 데이터를 들고오는데 where 조건에 해당되는 데이터만 들고온다. SELECT * FROM TABLE WHERE ID = *
findFirst - 해당 table에 where 조건에 해당되는 데이터를 들고오는데 createdAt이 최신 인 데이터만 들고온다. SELECT * FROM table WHERE ID = * ORDER BY ... LIMIT 1

async findAll(): Promise<Todo[] | null> {
	return await this.prismaService.todo.findMany();
}

async findOne(id: number): Promise<Todo | null> {
	return await this.prismaService.todo.findUnique({
      where: {
          id,
      },
  });
}

Prisma PATCH Method

update를 처리하는 방법은 총 2가지이다. update(), updateMany()

update - 해당하는 id의 레코드를 변경하는 것이다.
updateMany - 조건에 부합하는 레코드 전체를 변경하는 것이다.
둘 다 UPDATE ... WHERE ... 쿼리문을 갖고 있지만 update는 한 개의 레코드 / updateMany는 여러 개의 레코드를 한꺼번에 바꿀 때 사용한다.

async update(
	id: number,
    updateTodoDto: UpdateTodoDto,
): Promise<Todo | null> {
	return await this.prismaService.todo.update({
    	where: {
        	id,
        },
        data: {
        	todo: updateTodoDto.todo,
            isDone: updateTodoDto.isDone,
       },
   });
}

Prisma Delete Method

delete method 또한 현재 형식과 유사하다. delete(), deleteMany() 위와 동일해서 따로 코드를 작성하진 않겠다. 특이한 점으로는 Prisma를 이용하여 delete를 호출하면 return으로 해당 레코드를 반환한다.

async remove(id: number): Promise<Todo | null> {
	return await this.prismaService.todo.delete({
    	where: {
        	id,
       },
   });
} // 신기한 점 delete가 되면 해당 record를 return
profile
성실(誠實)한 사람만이 목표를 성실(成實)한다

0개의 댓글

관련 채용 정보