본문 바로가기
Back-end/기초부터 따라하는 nest.js

시즌 2 #11. 기초부터 따라하는 Nest.js 2 : Repository (DB에 쿼리 날리기)

by hsloth 2024. 7. 14.

지난번 포스팅에서는 Prisma를 이용해서 Nest.js에 DB를 연동하였습니다.

https://suloth.tistory.com/207

 

시즌 2 #10. 기초부터 따라하는 Nest.js 2 : Prisma와 DB 연결 (MySQL)

지난번 포스팅에서는 Pipe에 대해서 배웠습니다. https://suloth.tistory.com/206 시즌 2 #9. 기초부터 따라하는 Nest.js 2 : Pipe - 파이프지난번 포스팅에서는 Nest.js에서 Query와 Param, Body를 다루는 법에 대해

suloth.tistory.com

 

이번 포스팅에서는 PrismaService를 사용해서 쿼리를 날리는 방법과 Repository에 대해서 배워보겠습니다.


과제 정답


...

과제는.... 없었습니다. 네 ㅎ 이번 챕터는 쉬는 챕터였습니다.

 


Repository


기본적으로 ORM을 사용하는 방식은 두 가지가 있습니다.

첫 번째는 Active Record Pattern입니다.

Active Record Pattern 방식은 간단하게, 객체를 Class로 만들고 해당 Class에 메서드를 정의하여 호출하는 방식입니다. (모델에서 DB에 접근하는 방식)

예를들어 User라는 Class를 만들고, User를 대상으로 하는 동작(select, insert, update, delete)을 클래스의 메서드로 정의합니다.

객체 자체를 명확히 대상으로 하여 직관적이고 생산성이 높다는 장점이 있습니다.

 

두 번째는 Data Mapper Pattern입니다.

Data Mapper 방식은 간단하게, Service와 DB 사이에 Repository 계층을 두어 Repository에서 DB에 접근하는 방식입니다.

이렇게하면, Service와 DB를 분리할 수 있으므로 비즈니스 로직이 DB에 의존성을 띄지 않게되고, 유지보수에 도움이 된다는 장점이 있습니다.

 

자세한 것은 검색해서 알아보시길 바랍니다.

 

두 번째가 바로 Repository를 이용하는 패턴입니다.

 

자, 그럼 이제 repository를 어떻게 활용하는지 한 번 봐볼까요?

먼저, UserRepository를 만들어봅시다.

res/user에 user.repository.ts 파일을 만들고, UserModule에 UserRepository를 등록해줍시다.

// user.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/databases/prisma/prisma.service';

@Injectable()
export class UserRepository {
  constructor(private readonly prisma: PrismaService) {}
}

// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserRepository } from './user.repository';

@Module({
  controllers: [UserController],
  providers: [UserService, UserRepository],
})
export class UserModule {}

 

 

Repository에서 간단하게 User를 Create하는 로직을 작성해보도록 하겠습니다. 즉, 회원 가입이죠.

 

다음과 같이 작성할 수 있습니다.

// user.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/databases/prisma/prisma.service';

// Injectable 데코레이터를 통해 Nest.js가 UserRepository를 주입할 수 있도록(관리할 수 있도록) 한다.
@Injectable()
export class UserRepository {
  // PrismaService를 prisma라는 변수에 주입한다. (Nest.js 가 해줍니다)
  constructor(private readonly prisma: PrismaService) {}

  async createUser(
    email: string,
    hashedPassword: string,
    nickname: string,
  ): Promise<true> {
    // prisma를 이용하여 user 테이블에 데이터를 create 한다.
    await this.prisma.user.create({ // await는 그냥 DB작업이면 일단 다 await를 붙인다고 생각하세요.
      data: {  // user table에 들어갈 data 정의
        email,  // email: email 을 간소화한 문법입니다.
        password: hashedPassword,
        nickname, // nickname: nickname 을 간소화한 문법입니다.
      },
    });

    return true; // 생성이 정상적으로 되었다면, true를 반환합니다.
  }
}

 

유저를 생성할 때, email과 password, nickname이 꼭 필요하겠죠?

그래서 createUser의 인자로 email, hashedPassword, nickname을 받습니다.

비밀번호의 경우 암호화가 되어있어야 하기 때문에 hashedPassword를 받도록 하였습니다.

 

이런식으로, prisma에서는 Insert = create / Select = find / update = update / delete = delete 의 형태로 사용할 수 있습니다.

예를들어, SELECT * from User;this.prisma.user.find(); 함수로 대체해서 사용할 수 있습니다.

 

 

비밀번호를 암호화하기 위해 bcrypt라는 패키지를 다운로드 받아줍시다. 터미널에 다음을 입력해주세요.

npm i bcrypt

// bcrypt라이브러리의 타입을 사용하기 위한 패키지입니다.
// 꼭 설치하지 않아도 되지만, 원활한 타입추론을 위해는 설치하는 것을 권장합니다.
// 참고로 모든 라이브러리의 타입은 "@types/라이브러리" 에 존재합니다.
npm i --save-dev @types/bcrypt

 

 

그리고 UserService에서 다음과 같이 함수를 작성해줍시다.

// user.service.ts
import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UserService {
  // UserRepository를 생성자에 주입해주었습니다.
  constructor(private readonly userRepository: UserRepository) {}

  async getUserMainPage(): Promise<string> {
    return 'User Main Page';
  }

  async signUp(
    email: string,
    password: string, // 여기는 암호화가 되지 않은, 날 것 그대로의 비밀번호입니다.
    nickname: string,
  ): Promise<true> {
    // 비밀번호 암호화
    const hashedPassword = bcrypt.hashSync(password, 10);

    // 유저 생성
    const isCreated = await this.userRepository.createUser(
      email,
      hashedPassword,
      nickname,
    );

    return isCreated;
  }
}

 

 

여기서 잠깐, 왜 비밀번호 암호화를 repository에서 하지 않고, Service에서 진행하나요? 어차피 User를 생성할 때 무조건 비밀번호는 암호화 해야되잖아요?

더보기

음... 이건 뭐 개인취향이라고도 볼 수 있는데, 저의 경우는 Repository는 언제든지 갈아 낄 수 있는 레이어라고 생각하기 때문입니다.

만약, 중간에 ORM이 Prisma에서 TypeORM으로 바뀐다던가, NoSQL을 사용하게 된다던가 하는 등의 이유로 Repository가 바뀌는 일이 발생할 수 있습니다.

그 때, Repository의 코드에서 암호화 로직을 적어주기 귀찮을 것 같아서 이렇게 짰습니다.

그리고 개인적으로 비즈니스 로직은 모두 Service 레이어에 들어가고, "암호화" 또한 비즈니스 로직에서 중요한 부분 중 하나이기 때문에, 서비스 로직을 보면서 명확히 비밀번호를 암호화했다는 것을 보여주면 나중에 귀찮게 "비밀번호 암호화 했나?"를 Repository까지 들어가서 볼 필요가 없죠. 말 그대로 "Service의 흐름을 파악하려면 Service 로직만 보면 된다"를 위함입니다.

 

UserController에서는 원래 존재하던 bodyTest라는 함수를 다음과 같이 수정해줍시다. (bodyTest라는 함수가 없다면 @Post() 라는 데코레이터가 달린 함수를 수정해줍시다. @Post()에서 괄호 안의 인자는 url 경로이므로, url 경로가 중복되는 함수는 존재할 수 없기 때문입니다)

// user.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('/sign-up')
  async signUp(@Body() body: any): Promise<true> { // body를 받습니다.
    // body를 통해 받은 email, password, nickname을 사용해서 회원가입을 진행합니다.
    const isCreated = await this.userService.signUp(
      body.email,
      body.password,
      body.nickname,
    );

    return isCreated; // 회원가입이 정상적으로 완료되었다면 true를 반환합니다.
  }
}

 

음... 그냥 UserController를 위 코드를 복사붙여넣기 해주세요. 이미 존재했던 로직들은 사용하지 않을 것이기 때문에 지워도 상관없습니다.

 

그리고 서버를 실행해봅시다.

npm run start

 

서버가 실행되면 postman을 통해 회원가입 요청을 날려봅시다.

 

다음과 같이 설정하면 사진의 아래 부분의 Body에서 보이는 것처럼 true를 반환받을 수 있습니다. 그러면 계정이 성공적으로 생성되었다는 거죠!

(여기서, Request Body에 데이터를 담을 때, Postman을 보면 Body에서 JSON, raw 로 설정하는 걸 항상 잊지 마시길 바랍니다)

 

그러면 이제 유저가 생성되었는지 확인하러 가볼까요? 지난 시간에 MySQL Workbench를 사용하는 법을 알려드렸습니다.

 

localhost를 더블 클릭하고

 

study DB의 User 테이블에 마우스를 올리면 번개와 함께있는 표 아이콘이 나오는데

 

이 아이콘을 클릭해주면,

 

우리가 방금 생성했던 User가 들어있는 것을 볼 수 있습니다.

password또한 암호화가 되어서 들어가 있는 것을 볼 수 있죠.

 

만약 Workbench 사용이 어려우신 분들은 mysql CLI를 이용해서 접속해줍시다.

 

mysql -u root -p 명령을 통해 mysql 터미널에 접속하면 아래 명령어를 입력해줍시다.

use study;

select * from User;

 

 

 

그러면 위와 같은 테이블을 보실 수 있습니다!

 


과제


간단한 과제가 있습니다.

유저를 생성하기 위해서 this.prisma.user.create 구문을 사용했잖아요?

그러면 생성된 유저의 정보를 조회하기 위해서 find 구문을 사용해서 유저의 id로 유저를 조회하는 API를 한 번 만들어봅시다.

 

find문은 알려주지는 않았지만, prisma 공식문서를 읽는다던지 혹은 구글링을 통해 알아낸다던지 등의 방법으로 한 번 해봅시다.

 

 

 

 


이상으로 PrismaService를 이용해서 DB 쿼리 날리는 법과 Repository 레이어에 대해서 배워보았습니다.

 

오늘도 제 글을 읽어주셔서 감사합니다.