본문 바로가기

Sparta/TIL

24.03.12 TIL - TypeOrm

1. TypeOrm 설정

 

TypeORM은 typescript계의 Sequelizer라고 생각하면 된다. 쉽게 말해 타입스크립트에서 사용하는 ORM. Nest 환경에서는 아래의 명령어로 typeorm을 추가할 수 있다.

npm i @nestjs/typeorm typeorm mysql2
npm i @nestjs/config joi

그리고 추가적으로 validation을 위해 joi 패키지도 설치했다.

 

import Joi from 'joi';

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Post } from './post/entities/post.entity';
import { PostModule } from './post/post.module';

const typeOrmModuleOptions = {
  useFactory: async (
    configService: ConfigService,
  ): Promise<TypeOrmModuleOptions> => ({
    type: 'mysql',
    host: configService.get('DB_HOST'),
    port: configService.get('DB_PORT'),
    username: configService.get('DB_USERNAME'),
    password: configService.get('DB_PASSWORD'),
    database: configService.get('DB_NAME'),
    entities: [Post],
    synchronize: configService.get('DB_SYNC'),
    logging: true,
  }),
  inject: [ConfigService],
};

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        DB_HOST: Joi.string().required(),
        DB_PORT: Joi.number().required(),
        DB_USERNAME: Joi.string().required(),
        DB_PASSWORD: Joi.string().required(),
        DB_NAME: Joi.string().required(),
        DB_SYNC: Joi.boolean().required(),
      }),
    }),
    TypeOrmModule.forRootAsync(typeOrmModuleOptions),
    PostModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

  • imports 부분에 ConfigModule 부분
    • isGlobal: true는 전역적으로 적용한다고 선언
    • validationSchema에 정의된 스키마대로 .env에 정의되어 있어야 서버가 정상 동작함
  • typeOrmModuleOptions 옵션
    • 하드코딩된 설정을 주입하는게 아니라 동적으로 .env 파일에서 설정을 불러와서 주입
    • useFactory 기법을 사용하는데, TypeOrmModule.forRootAsync 함수에 전달되는 설정 객체를 동적으로 생성하기 위하여 사용하는 것. inject: [ConfigService]라는 옵션을 통해 의존성 주입
  • TypeOrmModule.forRootAsync - 동적으로 설정 주입 / forRoot : 하드코딩된 설정 사용

 

2. 엔티티 & 레포지토리

1) 개념

  • 엔티티 : 데이터베이스의 특정 테이블을 대표하는 객체로 ORM에서 사용됨. 이 객체를 통해 ORM 프레임워크가 DB와 통신, 테이블의 각 로우를 객체로 표현하며, 해당 객체의 속성을 테이블의 컬럼과 매핑됨.
  • 리포지토리: DDD(Domain-Driven Disign)에서 나온 개녕으로, 엔티티와 DB위 중간 계층을 형성하는 객체. 개발자가 DB와의 통신과정을 알지 못해도 추상화된 리포지토리의 함수를 사용하여 원하는 결과를 DB에서 불러올 수 있게 함. 리포지토리를 사용하면 도메인 로직은 DB의 세부 구현에서 분리되므로 유지보수성과 확장성이 향상

이전에 express에서 구현했던 3-layered-architecture는 레포지토리 부분이 파일로 존재했기에 처음엔 이해하는 것이 어려웠다. 아직 추상화라는 개념이 익숙치 않아서 그런 듯한데, 코드를 작성해보며 해결해야하는 과제라고 생각한다.

 

// post.entity.ts

import { IsNumber, IsString } from 'class-validator';
import {
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity({
  name: 'posts',
})
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @IsString()
  @Column('varchar', { length: 50, nullable: false })
  title: string;

  @IsString()
  @Column('varchar', { length: 1000, nullable: false })
  content: string;

  @IsNumber()
  @Column('int', { select: false, nullable: false })
  password: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @DeleteDateColumn()
  deletedAt?: Date;
}

 

예시 코드를 보면 @Entity 어노테이션은 해당 클래스가 어떤 테이블에 매핑되는지 나타내고 @PrimaryGeneratedColumn은 PK를 나타내며, @Column과 @DateColumn도 볼 수 있다. 비밀번호에 select 옵션은 일반적인 조회로 비밀번호를 얻어올 수 없게 하기 위한 설정으로, select절로 특정하지 않으면 반환하지 않게 해줘서 민감 정보를 보호할 수 있게 한다.

 

@DeleteDateColumn은 레코드가 삭제된 날짜가 자동으로 기록되는데, 해당 엔티티가 삭제되는 순간 실제로 삭제(Hard Delete)되는 게 아니라 논리적으로 삭제(Soft Delete) 되는 것이다.전체 게시물을 가져올 때 DeletedAt !== Null인 게시물만 가져와도 삭제된 것과 같은 결과를 낼 수 있다.

 

2) 리포지토리 생성

// post.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { Post } from './entities/post.entity';
import { PostController } from './post.controller';
import { PostService } from './post.service';

@Module({
  imports: [TypeOrmModule.forFeature([Post])],
  controllers: [PostController],
  providers: [PostService],
})
export class PostModule {}

 

특정 모듈 서비스에서 사용하고 싶은 리포지토리가 있다면 @Module 데코레이터의 imports 속성에 반드시 넣어야 DI가 활성화된다. 이제 Service 파일에서 생성자(constructure)를 통해 repository를 사용할 수 있게 된다. 생성자에서 @InjectRepository 어노테이션을 사용하여 리포지토리를 주입할 수 있고 일반 리포지토리로 DB연산이 부족하면 일반 리포지토리를 상속한 커스텀 리포지토리를 쓸 수 있다.

'Sparta > TIL' 카테고리의 다른 글

24.03.18 TIL - 정렬 for Javascript  (0) 2024.03.19
24.03.15 TIL - ELK stack  (0) 2024.03.18
24.03.11 TIL - nest.js 패키지  (0) 2024.03.12
24.03.08 TIL - Nest.js 파일 구조  (0) 2024.03.09
24.03.07 TIL - Typescript(3)  (0) 2024.03.08