Undergoing

02. Nest - PostgreSQL 연동 본문

개발/Nest

02. Nest - PostgreSQL 연동

Halkrine 2021. 8. 10. 11:11

1. TypeORM

- Object-Relational Mapping

- 객체와 RDB의 data를 자동으로 매핑해줌

- TypeScript와 함께 선호되는 기법 중 하나

 

2. TypeORM 설치

D:\IdeaProjects\nest-app>npm install @nestjs/typeorm typeorm pg
...
+ @nestjs/typeorm@8.0.2
+ pg@8.7.1
+ typeorm@0.2.36
added 54 packages from 93 contributors and audited 880 packages in 15.802s

84 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
D:\IdeaProjects\nest-app>npm install ts-node -g
...

+ ts-node@10.1.0
added 13 packages from 44 contributors in 1.175s

 

3. DB 연결

공식 가이드 문서에서는 다음과 같이 두 가지 기법으로 연결 방법을 제시함

- app.module.ts에 직접 연동

- root에 ormconfig.json 파일을 만들어 작성

 

나는 소스 관리 차원에서 후자를 택함

// ormconfig.json
{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "test",
  "password": "test",
  "database": "test",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true
}

* synchronize: true 설정은 production data가 손상될 수 있으니 production mode에서는 사용해서는 안 된다고 함

 

이후 app.module.ts에 관련 설정 추가

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestModule } from './test/test.module';
import {Connection} from "typeorm";

@Module({
  imports: [TypeOrmModule.forRoot(), TestModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  constructor(private readonly connection: Connection) {}
}

* connection은 지금 단계에서는 필요하지 않아 보이지만 일단 넣어두고 쓰임새는 나중에 파악하기로 함

 

상기 forRoot를 통해 객체를 전달할 수 있음. app.module.ts에서 ormconfig.json의 내용을 관리하려 한다면 이런 모양이 될 것

// app.module.ts

...
@Module({
...
  imports: [
    TypeOrmModule.forRoot({
  		type: "postgres",
  		host: "localhost",
  		port: 5432,
  		username: "test",
  		password: "test",
  		database: "test",
  		entities: ["dist/**/*.entity{.ts,.js}"],
  		synchronize: true
	}),
  ],
  ...
})

export class AppModule {}

* entity의 경로가 dist인 이유는 build 시 typescript 형태의 파일이 해당 경로에서 js파일로 recomplie 되어 node에서 읽어서 처리할 수 있게 됨. package.json 및 tsconfig.json 등에서 module param을 moulde로 처리하는 방법도 있는 것 같은데 우선 현재 기준에서 이해할 수 있는 구조로 가기로 함.

3. Test

TypeORM은 저장소 디자인 패턴을 지원, 각 항목에 자체 저장소가 존재함. 이 저장소는 db 연결에서 획득할 수 있음

커스텀해서 해보려 하다가 이왕에 처음부터 제대로 해보자는 생각에 관련 튜토리얼을 따라해보는 것부터 시작하려 함

 

우선 다음과 같은 구조 및 파일 생성함

src
└ test
   ├ dto
   │  └ create-test.dto.ts
   ├ test.controller.ts   
   ├ test.entity.ts
   ├ test.module.ts
   ├ test.repository.ts
   └ test.service.ts
  • create-test.dto.ts : table 생성 관련 dto. DB는 만들어져 있는 환경에서 테스트하느라 이 dto는 만들어두고 사용하지는 않음.
import { IsNotEmpty } from 'class-validator'; // data 전송 시 해당 field의 유효성을 검사하는 lib

export class CreateUserDto {
  @IsNotEmpty()
  readonly first_name: string;

  @IsNotEmpty()
  readonly last_name: string;

  @IsNotEmpty()
  readonly usr_id: string;

  @IsNotEmpty()
  readonly secret: string;
}
  • test.controller.ts
import { Controller, Get, Param, Query, Req } from '@nestjs/common';
import { TestService } from './test.service';
import { TestEntity } from './test.entity';

@Controller('test')
export class TestController {
  constructor(private readonly testService: TestService) {}

  @Get('/findName')
  async findName(
    @Query('first_name') first_name: string,
    @Query('last_name') last_name: string,
  ): Promise<TestEntity[]> {
    const ret = this.testService.findName(first_name, last_name);
    return ret;
  }
}
  • test.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert } from 'typeorm';

@Object
@Entity({name:"tb_user", schema:"public"})
export class TestEntity {
  @PrimaryGeneratedColumn() // 이 Annotation을 통해 기본키로 사용함을 명시함
  id: string;

  @Column()
  company_name: string;

  @Column({ nullable: false })
  first_name: string;

  @Column({ nullable: false })
  last_name: string;

  @Column({ nullable: false })
  secret: string;

  @Column({ nullable: false })
  usr_id: string;
}

* entity annotation에 대해서는 몇 가지 옵션이 존재하는데, name이나 schema 옵션을 부여하면 해당 entity의 활용처를 명시적으로 지정할 수 있다. 차후 이를 잘 활용하면 모델단이 필요 이상으로 지저분해질 일이 없을 것 같다.

import { OrderByCondition } from "../../find-options/OrderByCondition";
/**
 * Describes all entity's options.
 */
export interface EntityOptions {
    /**
     * Table name.
     * If not specified then naming strategy will generate table name from entity name.
     */
    name?: string;
    /**
     * Specifies a default order by used for queries from this table when no explicit order by is specified.
     */
    orderBy?: OrderByCondition | ((object: any) => OrderByCondition | any);
    /**
     * Table's database engine type (like "InnoDB", "MyISAM", etc).
     * It is used only during table creation.
     * If you update this value and table is already created, it will not change table's engine type.
     * Note that not all databases support this option.
     */
    engine?: string;
    /**
     * Database name. Used in Mysql and Sql Server.
     */
    database?: string;
    /**
     * Schema name. Used in Postgres and Sql Server.
     */
    schema?: string;
    /**
     * Indicates if schema synchronization is enabled or disabled for this entity.
     * If it will be set to false then schema sync will and migrations ignore this entity.
     * By default schema synchronization is enabled for all entities.
     */
    synchronize?: boolean;
    /**
     * If set to 'true' this option disables Sqlite's default behaviour of secretly creating
     * an integer primary key column named 'rowid' on table creation.
     * @see https://www.sqlite.org/withoutrowid.html.
     */
    withoutRowid?: boolean;
}
  • test.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TestController } from './test.controller';
import { TestService } from './test.service';
import { TestEntity } from './test.entity';

@Module({
  imports: [TypeOrmModule.forFeature([TestEntity])],
  exports: [TypeOrmModule],
  providers: [TestService],
  controllers: [TestController],
})
export class TestModule {}
  • test.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm/dist/common/typeorm.decorators';
import { Repository } from 'typeorm/repository/Repository';
import { TestEntity } from './test.entity';

@Injectable()
export class TestService {
  constructor(
    @InjectRepository(TestEntity)
    private readonly testRepository: Repository<TestEntity>,
  ) {}

  findName(first_name: string, last_name: string): Promise<TestEntity[]> {
    console.log("name : " + first_name + " " + last_name);
    return this.testRepository.find({
      where: { first_name, last_name },
    });
    //console.log(Logger.log(""));
    //return found;
  }
}

* 기본적인 검색어는 Query Annotation으로 파라미터 처리하고, 서비스단에서 바로 리턴

 

find 구문 내에 상기 코드와 같이 할 경우 SELECT * 가 되지만, select : ["column1", column2", ...] 를 통해 필요 컬럼만 return 받을 수도 있다.

 

TypeORM의 Repository 외에, 해당 쿼리용 Repository 파일을 만들어 처리하는 방식도 있는데, 이는 아직 이해가 제대로 안 되어 공통 Repository로 처리함

 

 

 

 

TypeORM 공식 페이지

'개발 > Nest' 카테고리의 다른 글

03. Next.js 붓기  (0) 2021.08.12
01. Nest  (0) 2021.08.04