Act99 기술블로그

[Nestjs] CSV 파일을 postgresql 테이블로 옮기고 localhost 에 띄우기 본문

개발팁저장소/nestjs

[Nestjs] CSV 파일을 postgresql 테이블로 옮기고 localhost 에 띄우기

Act99 2021. 11. 24. 21:26

필자는 현재 주식정보를 RestAPI 로 띄운 후, React를 이용해 브라우저 상에서 주식 차트를 구현시키려고 한다.

프론트엔드 공부를 하려했지만, 주식 api를 직접 빌드해서 사용하고 싶었기 때문에 작업을 시작했다.

 

DB 관리는 postgresql 로 할 예정이며, Graphql 과 Typeorm, Nestjs 를 이용할 예정이다.

(Nestjs를 배울 때 이 방식으로 배웠기 때문이기도 하며,

Nestjs Doc을 보면 Typeorm과 Graphql 사용을 권장한다고 적혀있다.)

** postgresql 테이블에 CSV파일을 import 하고 nestjs와 연결하려면 Table을 직접 만드는게 아니라 Nestjs 를 이용해 table을 만들어주어야한다. ** (테이블을 미리 만들어놓고하다가 연동이 안되서 1시간을 잡아먹었다.)

 

먼저해야할 일은 NestJs에서 모듈을 만드는 일이다.

테스트용으로 가져온 주식 정보는 gs 주식이기 때문에 모듈이름을 stock-gs 로 지었다.

$ nest g mo stock-gs

다음으로 Service 와 Resolver, Entity 를 만들어준다. (graphql을 이용해 데이터들을 불러올 것이기 때문에 Resolver를 필수로 만들어야 한다. )

 

entity의 경우 내가 가져올 CSV 파일 Column 명에 맞추어 작성했다.

Entity는 꼭 @PrimaryGeneratedColumn이 필요하며 없을 경우 에러가 생긴다.

작성한 결과는 다음과 같다. (Column이 많아서 좀 깁니다.)

 

- stock-gs.entity.ts

import { Field, Float, InputType, ObjectType } from '@nestjs/graphql';
import {
  IsBoolean,
  IsNumber,
  IsOptional,
  IsString,
  isString,
} from 'class-validator';
import { CoreEntity } from 'src/common/entities/core.entity';
import { Users } from 'src/users/entities/users.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';

// 만약 dto 부분에 omittype에 inputtype 으로 호출하라 라는 명령어가 없다면 아래를 써줘야함.
// @InputType({isAbstract:true})
// @Field() => 얘는 graphql 데코레이션이며
// @Column(), @ManyToOne() => 얘는 typeorm 데코레이션임
@ObjectType()
@Entity()
export class Gs {
  @PrimaryGeneratedColumn()
  @Field((type) => Number)
  @IsNumber()
  id: number;

  @Field((type) => Number)
  @Column()
  @IsString()
  index: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  date: number;

  @Field((type) => String)
  @Column()
  @IsString()
  check_item: string;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  code: number;

  @Field((type) => String)
  @Column()
  @IsString()
  code_name: string;

  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  d1_diff_rate: number;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  close: number;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  open: number;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  high: number;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  low: number;

  @Field((type) => Number)
  @Column()
  @IsNumber()
  volume: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo5: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo10: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo20: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo40: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo60: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo80: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo100: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  clo120: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo5_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo10_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo20_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo40_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo60_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo80_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo100_diff_rate: number;
  @Field((type) => Float)
  @Column('float')
  @IsNumber()
  clo120_diff_rate: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo5: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo10: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo20: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo40: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo60: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo80: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo100: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  yes_clo120: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol5: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol10: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol20: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol40: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol60: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol80: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol100: number;
  @Field((type) => Number)
  @Column()
  @IsNumber()
  vol120: number;
}

 

다음 작업해야하는건 Service이며, 데이터 수정이나 삭제 없이, 정보만 가져올 예정이기 때문에 다음과 같이 작성했다.

 

- stock-gs.module.ts

import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Gs } from './entities/stock-gs.entity';

export class StockGsService {
  constructor(@InjectRepository(Gs) private readonly stockGs: Repository<Gs>) {}
  getStock(): Promise<Gs[]> {
    return this.stockGs.find();
  }
}

@InjectRepository 를 사용하지 않으면 Postgresql에 table이 형성되지 않는다.

 

그 후, resolver를 작성한다. 앞서 말한대로 정보만 가져올 것이기 때문에 @Mutation은 필요 없고 @Query 데코만 사용하면 가능하다.

 

- stock-gs.resolver.ts

import { Query, Resolver } from '@nestjs/graphql';
import { Gs } from './entities/stock-gs.entity';
import { StockGsService } from './stock-gs.service';

@Resolver((of) => Gs)
export class StockGsResolver {
  constructor(private readonly stockGsService: StockGsService) {}
  @Query((returns) => [Gs])
  gsStock(): Promise<Gs[]> {
    return this.stockGsService.getStock();
  }
}

 

resolver 작성이 완료되면 app.module에 연동시켜준다.

resolver와 service는 providers에 넣어준다. (Nestjs provider def 참조)

 

- stock-gs.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Gs } from './entities/stock-gs.entity';
import { StockGsResolver } from './stock-gs.resolver';
import { StockGsService } from './stock-gs.service';

@Module({
  imports: [TypeOrmModule.forFeature([Gs])],
  providers: [StockGsResolver, StockGsService],
})
export class StockGsModule {}

 

stock-gs module 완성

app.module.ts 파일에는 자동으로 Stockgs모듈이 import되어있다. (만약 안되어있으면 직접 해야한다.)

TypeOrmModule.forRoot 쪽 entities에 Gs entity를 넣어준다. (Users 는 회원가입까지 가능하게 만들기 위해 필자가 임시로 만들어놓았다.)

 

    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      synchronize: process.env.NODE_ENV !== 'prod',
      logging: process.env.NODE_ENV !== 'prod',
      entities: [Users,Gs],
    }),

여기까지 NestJs 작업을 마치고 실행시켜준다.

$ npm run start:dev

 

gs 주식 데이터가 없다가
gs 테이블이 나온것을 확인할 수 있다.

 

column 도 정확하게 들어가있다.

 

이제 CSV 파일을 import 시켜주어야한다.

pgadmin4 를 이용하면 쉽게 import 할 수 있다.

주의해야하는 점은 import column 에서 id 는 빼주어야 한다. (id 는 PK 이므로)

테이블에 오른쪽 클릭한 후 import를 누른다.

 

import 로 바꾼 후 csv파일로 포맷을 설정하고 Delimiter 를 , 로 바꾼다. (CSV파일이기 때문)

 

만약 import columns 쪽에 id가 포함되어있다면 없애준다.

 

데이터가 잘 들어와있다.

 

 

데이터가 잘 들어와있으면 graphql 을 확인해준다.

localhost/xxxx/graphql에 접속한다.

 

query 입력하고 확인한다.

 

이로써 주식데이터를 localhost상에서 확인 가능하게 되었다.

다음으로는 React와 연동을 시켜 그래프를 만들 예정이다.