본문 바로가기

Development/Web

Nest JS #3 - PostgresSQL에서의 TypeORM(Entity, Repository) 생성

들어가며

이전까지는 Nest JS의 기능을 알아보는데 더 중점을 두기 위해 로컬 메모리 방식의 데이터베이스를 사용하였다. 지금부터는 애플리케이션에 실제 데이터베이스를 연결하고, TypeORM, Entity, Repository에 대해서 정리해보겠다.

PostgresSQL 설치

PostgresSQL은 객체-관계형 데이터베이스 시스템으로 전 세계 사용자가 4번째(OracleDB, MySQL, MsSQL, PostgresSQL)로 많은 RDBMS이다. 이는 MySQL에 비해 SQL 표준을 더 많이 지워하며, 쿼리가 복잡할수록 성능면적으로 더 우수하다는 특징이 있다. 영상에서는 로컬에 PostgresSQL을 설치했지만, 나는 Docker Container 환경에서 PostgresSQL을 실행해주었고, DBeaver를 통해 데이터베이스(board-app)에 접속하였다.

version: '3.1'
services:
  postgres:
    image: postgres
    restart: always
    container_name: postgres-client
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres      
    volumes:
      - ./postgres:/var/lib/postgresql/data
    ports:
      - 3001:5432

  adminer:
    image: adminer
    restart: always
    container_name: postgres-admin
    ports:
      - 3002:8080
$ docker-compose up

TypeORM

TypeORM(Object Relational Mapping)은 Node.js에서 실행되고 TypeScript로 작성된 객체 관계형 Mapper 라이브러리이다. 이는 MySQL, PostgresSQL, MariaDB, SQLite, MsSQL, Oracle, SAP Hana 및 WebSQL과 같은 여러 데이터베이스를 지원한다. ORM은 객체와 관계형 데이터베이스의 데이터를 자동으로 변형 및 연결하는 작업으로, ORM을 이용하여 개발하면 객체와 데이터베이스 간 변환 시 유연하게 사용할 수 있다.

객체지향 프로그래밍에서의 객체는 주로 클래스이며, 관계형 데이터베이스의 데이터는 테이블 형태로 존재한다. 즉, 객체와 관계형 모델의 불일치로 인해 관계형 모델을 객체로 변환시키는 과정이 매우 복잡하다는 문제가 있다. 예를 들어, Pure JavaScript로 데이터를 가져오는 코드를 살펴보면 다음과 같다.

const query = `SELECT * FROM boards WHERE title="Hello" AND status="PUBLIC"`;
db.query(query, (err, res) => {
    if (err) throw new Error('error');
    boards = res.rows;
});

반면, ORM을 사용하면 단순한 코드로 쿼리를 실행할 수 있다.

const boards = Board.find({ title: 'Hello', status: 'PUBLIC'});

즉, TypeORM은 데이베이스의 테이블 체계를 모델을 기반으로 자동으로 생성하고, 그로인해 데이터베이스에서 개체를 쉽게 다룰 수(CRUD) 있으며, 테이블 간의 Mapping(일대일, 일대다, 다대다)을 만들 수 있고, 간단한 CLI 명령을 제공한다는 특징이 있다. 또한, TypeORM은 간단한 코딩으로 ORM 프레임 워크를 사용하기 쉽고, 다른 모듈과 쉽게 통합된다는 장점이 있다.

TypeORM 모듈 설치

TypeORM을 사용하기 위해 설치해야 하는 모듈은 @nestjs/typeorm, typeorm, pg가 있으며, 이들은 각각 Nest JS에서 TypeORM을 사용하기 위해 연동시켜주는 모듈, TypeORM 모듈, Postgres 모듈이다.

$ npm i -s @nestjs/typeorm typeorm pg

Nest JS의 공식문서에 TypeORM으로 데이터베이스를 컨트롤 하는 부분이 자세히 나와 있으니 참고하기 바란다.

TypeORM 연결

이제부터 애플리케이션에 TypeORM을 연결해보도록 하겠다. 먼저, TypeORM 설정을 위해 src 디렉토리에 configs 디렉토리를 생성하고, typeorm.config.ts 파일을 생성한 후 다음과 같은 코드를 입력한다.

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeORMConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: 'localhost',
  port: 3001,
  username: 'postgres',
  password: 'postgres',
  database: 'board-app',
  entities: [`${__dirname}../**/*.entity.{js, ts}`],
  synchronize: true,
};

entities를 이용해서 데이터베이스 테이블을 생성해주는데, Entity를 하나씩 입력할 수도 있지만, 위의 코드와 같이 모든 Entity를 포함하도록 작성할 수도 있다. Entity를 하나씩 입력하는 경우에는 다음과 같이 작성하면 된다.

entities: [User, Board]

synchronize를 true로 하게 되면 애플리케이션을 다시 실행할 때 entities에 포함된 테이블을 Drop한 후 다시 생성한다. 만약, production 모드로 애플리케이션을 실행하려는 경우에는 synchronize를 false로 지정해주야 한다.

위에서 작성한 설정 파일을 아래와 같이 애플리케이션의 root Module(app.module.ts)에 연결시켜주어야 한다.(v.0.1.8)

@Module({
  imports: [TypeOrmModule.forRoot(typeORMConfig), BoardsModule],
})

Entity

ORM 없이 데이터베이스 테이블을 생성할 때에는 아래와 같은 쿼리문을 작성하여야 한다.

CREATE TABLE board {
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description VARCHAR(255) NOT NULL,
    status VARCHAR(7) NOT NULL
}

반면, ORM을 사용하는 경우에는 데이터베이스 테이블로 변환되는 Class로 처리하기 때문에 Class를 생성한 후 그 안에 컬럼들을 정의해주면 된다.

@Entity()
export class Board extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  status: BoardStatus;
}

위 코드에서 나온 @Entity 데코레이터는 Board Class가 Entity임을 나타내기 위해 사용하며, 이는 쿼리문 중에 CREATE TABLE board에 해당한다. 그리고 @PrimaryGeneratedColumn() 데코레이터는 id 열이 Board Entity의 기본 키에 해당하는 열을 나타낸다. @Column 데코레이터는 Board Entity의 title, description과 같은 다른 열을 나타내는데 사용된다. Entity를 생성하기 위해 BoardModule 디렉토리에 board.entity.ts 파일을 생성한 후 위의 코드와 같이 작성한다(v.0.1.9).

Repository

Repository는 Entity 개체와 함께 작동하며, Entity를 조회하거나, 삽입 또는 수정하는 작업을 수행한다. 이처럼 데이터베이스에 관련된 일은 Controller가 아닌 Repository에서 처리하게 되는데 이를 Repository Pattern이라고 한다.

Repository를 생성하기 위해 BoardModule에 해당하는 디렉토리에 board.repository.ts 파일을 생성한 후 다음과 같은 코드를 입력한다.

import { EntityRepository, Repository } from 'typeorm';
import { Board } from './board.entity';

@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {}

위 코드에서 @EntityRepository 데코레이터는 Class를 사용자 정의 Repository로 선언하는데 사용되며, 사용자 지정 Repository는 일부 특정 Entity를 관리하거나 일반 저장소로 사용될 수 있다. 또한, Find, Insert, Delete 등 Entity를 제어할 수 있도록 Repository를 상속받는다. 이렇게 생성한 Repository를 BoardModule 내에서 사용할 수 있도록 아래 코드와 같이 board.module.ts에 해당 Repository를 추가한다(v.0.2.0).

@Module({
  imports: [TypeOrmModule.forFeature([BoardRepository])],
  controllers: [BoardsController],
  providers: [BoardsService],
})

마치며

여기까지 데이터베이스 설치부터 ORM 사용을 위한 Entity 생성과 Entity를 컨트롤하기 위한 Repository 생성 및 등록에 대해서 정리해보았다. 다음에는 앞서 구현해놓은 로컬 메모리 방식의 데이터베이스 기능을 실제 데이터베이스를 사용한 코드로 수정해주는 내용에 대해서 정리해보겠다.

GitHub

참고자료