본문 바로가기
Research/Nest.js

09. Nest.js_ Database 준비

by RIEM 2023. 2. 7.

도커 기반으로 nest.js 앱을 DB에 연동하는 방법을 알아보겠습니다.

Database 준비 및 생성

Docker란?

도커는 기기에 상관없이 같은 구동환경을 세팅해주는 도구입니다. 팀이 같은 조건에서 작업을 할 수 있기 때문에 협업에도 유용합니다.

Screen Shot 2023-02-07 at 5 29 25 PM

이미지(원본) -----> 컨테이너(인스턴스)

  • image: 이미지는 원본의 형상. 컨테이너의 템플릿. MySQL, Postgres image와 같이 pre-built된 이미지도 있는 반면 Dockerfile로 커스텀 이미지를 만들 수도 있
  • Docker file : Image에 대한 정의가 담긴 파일. 쉽게 말하면 이미지를 묘사한 쉘 명령어들이 모인 텍스트 파일. 이 명령어들을 기반으로 소프트웨어들을 설치
  • container : 컨테이너는 image로부터 만들어진 인스턴스
  • Docker Compose : 설정한 DB를 동시에 사용

Docker Compose로 DB 실행하기

docker-compose.yml 파일 생성

루트 디렉터리에 yml 파일을 생성해주자.

주요 내용으로는

  • 배열은 -로 시작
  • restart : 컨테이너 재시작
  • platform : 맥북 m1 을 사용중이기 때문에 linux/amd64로 설정해주었다
version: '3.8'

services:
  mysql:
    image: mysql:8.0.23
    platform: linux/amd64
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
    ports:
      - 3306:3306

  postgres:
    image: postgres:13.1
    restart: always
    environment:
      POSTGRES_PASSWORD: example
    ports:
      - 5432:5432

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

docker-compose 실행

  1. 도커 앱을 켠다
  2. nest.js 앱 루트 디렉토리에서 docker-compose up 커맨드 실행

뭔가 무지막지하게 로그가 뜰 것이다.

DB admin 접속

Screen Shot 2023-02-07 at 5 46 17 PM
8080포트로 설정해준 adminer로 접속해주자. mac os의 경우 Server 필드에 mysql이라고 기재해줘야 한다.

Screen Shot 2023-02-07 at 5 47 45 PM
DB 생성 버튼을 누르고 위와 같이 값을 입력한 뒤 save를 누른다.

Screen Shot 2023-02-07 at 5 48 40 PM
nest-events 데이터베이스가 정상적으로 생성되었다.

DB 연동 및 테이블 생성

ORM 관련 지식

ORM이란

  • Object-Relational Mapping의 약자다
  • 객체 지향적으로 DB 스키마를 추상화시켜준다
  • sql 쿼리 없이 DB를 다룰 수 잇게 해준다

TypeORM이란?

  • Node.js의 ORM 라이브러리다
  • Nest.js 모듈에서 TypeORM integration을 제공해준다

Entity란?

  • JS 또는 TS 클래스에 매핑되는 DB 테이블

Repository

  • 특정 Entity에 데이터 접근 권한을 제공한다

Query Builder

  • 객체 지향적으로 sql 쿼리를 만들 수있도록 허용한다

DB에 연동하기

Nest.js 앱을 DB와 연동해보자

패키지 설치

npm i --save @nestjs/typeorm typeorm mysql

모듈에서 Typeorm import

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventsController } from './events.controllers';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    // TypeORM 임포트
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: '127.0.0.1',
      port: 3306,
      username: 'root',
      password: 'example',
      database: 'nest-events'
    }),
  ],
  controllers: [AppController, EventsController], // 이벤트 컨트롤러 추가
  providers: [AppService],
})
export class AppModule {}

도커 생성 후 앱 실행

docker-compose up
npm run start:dev

Entity(PK, Colums)

Entity는 DB 테이블과 매핑되는 클래스다

entity 작성

// src/event.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Event {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  description: string;

  @Column()
  when: Date;

  @Column()
  address: string;
}

참고할 만한 정보로는 아래와 같습니다

  • PrimaryGeneratedColumn()은 자동 생성되는 PK 컬럼을 지칭한다. Mysql에서는 auto increment, Postgres는 serial, Oracle에서는 sequence 개념
  • @PrimaryColumn() 은 주민등록번호, 전화번호 등에 적합한 컬럼이다
  • Composite Key는 두개 이상의 컬럼으로 구성된 키를 뜻한다

앱 모듈에 entity 추가

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventsController } from './events.controllers';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Event } from './event.entity';

@Module({
  imports: [
    // TypeORM 임포트
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: '127.0.0.1',
      port: 3306,
      username: 'root',
      password: 'example',
      database: 'nest-events',
      entities: [Event], // 이벤트 entity 추가
      synchronize: true, // local에서만 true로 주로 사용. 실행 시 매번 DB 편하게 생성. production 모드에서는 사용하지 않는 것이 좋음
    }),
  ],
  controllers: [AppController, EventsController], // 이벤트 컨트롤러 추가
  providers: [AppService],
})
export class AppModule {}

Entity 적용 확인

Screen Shot 2023-02-07 at 6 13 53 PM

DB의 event 테이블이 정상적으로 생성되었습니다. 하지만 name 컬럼의 길이가 255인데, 너무 길다고 생각됩니다. when 컬럼 명도 너무 모호하여 좀 더 명시적으로 수정해주겠습니다.

Entity 수정

// src/event.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Event {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 }) // 길이가 너무 길어 255 -> 100로 수정
  name: string;

  @Column()
  description: string;

  @Column({ name: 'when_date' }) // 모호한 컬럼 명을 when -> when_date로 수정
  when: Date;

  @Column()
  address: string;
}

Screen Shot 2023-02-07 at 6 19 10 PM
name의 길이와 when의 컬럼명이 잘 수정되었습니다.

Repository Pattern 적용

Repository Pattern이란?

Entity가 양이라면 Reposity는 양치기 개입니다. Repository가 Entity들을 이리저리 잘 끌고다니기 때문입니다.

Repository 패턴은 ORM에 잘 맞는 패러다임입니다.

Repository Pattern이란 데이터 소스 레이어와 비즈니스 레이어 사이에 Repository 레이어를 추가하여 중재를 하도록 하는 패턴입니다. 즉, 비즈니스 비즈니스 레이어는 그냥 요청만 하고 쿼리 작성과 같은 디테일한 부분은 Repository 레이어에서 처리해주겠다는 것입니다.

Repository Pattern의 장점

  • 데이터 로직과 비즈니스 로직을 분리하기 때문에, 비즈니스 레이어에서는 DB 관련 로직은 신경쓰지 않아도 된다
  • 도메인에서 데이터 요청이 일관적으로 바뀐다. Respository가 추상화되어있기 때문
  • 데이터 저장소의 데이터를 캡슐화한다
  • 단위 테스트 검증이 수월해진다
  • 객체 간 결합도가 감소한다
  • 어플리케이션 아키텍처가 바뀌어도 영향이 적다

Screen Shot 2023-02-07 at 7 33 05 PM

Generic Repository는 TS의 Generic를 사용합니다. save, find, findOne 등 기본적인 메소드를 가지고 있습니다. 대부분의 CRUD 수행은 Generic Repository로 충분합니다.

Specific Repository는 특별히 자주 사용하는 메소드들을 위해 사용합니다.

의존성 주입으로 Repository 등록

// src/events.controllers.ts
import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  HttpCode,
} from '@nestjs/common';
import { CreateEventDto } from './create-event.dto';
import { UpdateEventDto } from './update-event.dto';
import { Event } from './event.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

@Controller('/events')
export class EventsController {
  // 생성자 주입 시, Entity 클래스 인자로 받는 InjectRepository 데코레이터 사용
  constructor(
    @InjectRepository(Event)
    private readonly repository: Repository<Event>
  ){}

  // 5개 정도 가볍게 유지하는 것이 좋다
  @Get()
  async findAll() {
    // 배열을 반환하여도 클라이언트는 JSON 데이터로 받는다
    return await this.repository.find();
  }
  // Param 데코레이터 사용하여 파라미터 가져오기
  @Get(':id')
  async findOne(@Param('id') id) {
    const event = await this.repository.findOne(id);
    return event;
  }

  // Body 데코레이터로 body 값 가져오기
  @Post()
  async create(@Body() input: CreateEventDto) {
    // Dto에서 타입 규정했기에 프로퍼티, 타입이 통제
    return await this.repository.save({
      ...input,
      when: new Date(input.when), // dto에는 규정되었지만 input에 없는 데이터 추가
    });
  }

  @Patch(':id')
  async update(@Param('id') id, @Body() input: UpdateEventDto) { 
    const event = await this.repository.findOne(id);

    return await this.repository.save({
      ...event,
      ...input,
      when: input.when ? new Date(input.when) : event.when,
    });
  }
  @Delete(':id')
  @HttpCode(204)
  async remove(@Param('id') id) {
    const event = await this.repository.findOne(id);
    await this.repository.remove(event);
  }
}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventsController } from './events.controllers';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Event } from './event.entity';

@Module({
  imports: [
    // TypeORM 임포트
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: '127.0.0.1',
      port: 3306,
      username: 'root',
      password: 'example',
      database: 'nest-events',
      entities: [Event], // 이벤트 entity 추가
      synchronize: true, // local에서만 true로 주로 사용. 실행 시 매번 DB 편하게 생성. production 모드에서는 사용하지 않는 것이 좋음
    }),
    // 모듈에서 사용할 리파지토리 등록 메소드
    TypeOrmModule.forFeature([Event]),
  ],
  controllers: [AppController, EventsController], // 이벤트 컨트롤러 추가
  providers: [AppService],
})
export class AppModule {}

목차

DB 준비 및 DB 생성

  1. Docker file 작성
  2. docker-compose 실행
  3. Adminer 접속하여 DB 직접 생성

DB 연동하여 테이블 생성

  1. 패키지 설치
  2. 모듈에서 typeorm 임포트
  3. entity 작성
  4. 모듈에 entity 추가
  5. Entity 적용 확인
  6. Entity 수정

Repository 패턴 적용하기

추가 참조

댓글