본문 바로가기
이론/Backend

Nest.js에서 로컬 DB 연결하기

by 유세지 2023. 2. 5.

 

사이드 프로젝트로 진행중인 조리복 프로젝트의 본격적인 백엔드 구현이 시작되었습니다. 백엔드에 대한 경험이 없다보니 전체적인 그림을 그릴 능력이 부족해서, 하나씩 직접 부딪혀보면서 진행하고 있습니다. 순서가 이상하고 뒤엉켜도 삽질하는 과정이겠거니 하고 읽어주시면 감사하겠습니다.

 

지난 시간까지는 CRUD 생성기로 기본적인 틀을 가져오고, 실제 프로젝트에서 사용할 몇 가지 진입점만 함께 구현해놓았었습니다.

 

오늘의 목표는 로컬 DB에 연결시켜서 정상적으로 서버가 작동하는지 확인하는 것입니다.

 

 

로컬 DB 만들기

 

DB로는 MySQL을 사용하기로 하였습니다. 무료로 사용할 수 있는 MySQL Workbench가 있어서 사용하기로 하고, 사전 설정을 완료했습니다. 원래대로라면 로컬 서버를 세팅하고 연결하는 과정을 거쳐야 했지만, 제 경우엔 이전에 했던 프로젝트에서 미리 세팅했던 로컬 서버가 있어 조리복용 스키마만 얹어서 그대로 사용했습니다. 일단 연결이 되는지를 확인하기 위함이니까요.

 

 

생성된 커넥션 (기억은 안나지만 있던것)

 

 

 

 

DB 연결 설정

 

이제 프로젝트와 DB를 연결할 차례입니다. 우선 연결에 필요한 모듈부터 설치하겠습니다.

 

npm install @nestjs/config cross-env mysql2 typeorm
npm install @nestjs/typeorm --save-dev

 

총 5가지 모듈을 사용했습니다. 하나씩 살펴보면 아래와 같습니다.

 

  • @nestjs/config : dotenv 를 기반으로 하는 Nest의 구성 모듈입니다. 환경변수 설정을 위해 설치합니다.
  • cross-env : 마찬가지로 환경변수 설정을 위해 사용합니다.
  • mysql2 : MySQL과 연결하기 위해 사용합니다.
  • typeorm : TypeORM을 사용하기 위해 설치합니다. TypeORM에 관해서는 이후에 자세히 설명하겠습니다.
  • @nestjs/typeorm : 마찬가지로 nest에서 TypeORM을 사용하기 위해 설치합니다.

 

설치가 모두 완료되었다면, 엔티티를 설정합니다.

 

기존의 자동 생성된 엔티티 파일에는 내부 구현이 하나도 없었습니다. 완벽하게는 아니지만, 이후에 다시 짤 예정이니 대략 사용할 필드정도만 만들어서 채워주겠습니다.

 

// 자동 생성된 User.entity.ts
export class User {}

 

// 수정한 user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

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

  @Column()
  userId: string;

  @Column()
  password: string;

  @Column()
  nickname: boolean;
}

 

이런식으로 User, Article, Lucky 세 개의 엔티티를 만들었습니다. 다음으로는 가장 상위의 app.module.ts에서 이 엔티티들을 가져와 연결해주어야 합니다.

 

 

// 기존 자동 생성된 app.module.ts
@Module({
  imports: [UserModule, ArticleModule, LuckyModule],
  imports: [
    UserModule,
    ArticleModule,
    LuckyModule,
  ],
  controllers: [AppController, UserController, ArticleController, LuckyController],
  providers: [AppService, UserService, ArticleService, LuckyService],
})

 

 

// 변경한 app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    UserModule,
    ArticleModule,
    LuckyModule,
    TypeOrmModule.forRoot({
      type: '',
      host: '',
      port: 3000,
      username: '',
      password: '',
      database: '',
      entities: [User, Article, Lucky],
      synchronize: false,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

 

기존의 컨트롤러에 각 도메인에 해당하는 컨트롤러와 프로바이더가 모두 입력되어 있었는데, 실제로는 도메인별 Module에서도 이미 불러오고 있어 중복이라는 생각이 들어 지워주었습니다. 실제로 지우더라도 동작은 동일했습니다.

 

TypeOrmModule의 값이 비어있는데, 위 코드에서만 비워 둔 것이고 실제로는 각 필드에 값을 입력한 뒤 테스트를 진행하였고 정상적으로 연결되는 것을 확인했습니다.

 

DB 연결에는 성공했지만 이대로 파일을 올리게 되면 보안상의 문제가 발생할듯 보였습니다. 프론트에서도 서버에 접근하는 주소나 계정 등 민감한 데이터는 별도의 환경변수로 빼낸 뒤 관리했기 때문에 백엔드에도 당연히 이런게 있을거라 생각했습니다. nest의 환경 변수 설정에 대해 검색해 본 결과, @nest/config가 바로 그 역할을 수행할 수 있었습니다. (위에서는 편의상 한번에 설치했지만 실제로는 이렇게 찾아가며 설치했습니다.)

 

 

우선 필요한 정보를 별도의 환경 변수 파일로 옮겼습니다. package.json에서 적용해줄테니 편의상 이 파일과 같은 경로에 두었습니다.

 

// .env
DB_HOST=루프백주소
DB_PORT=포트
DB_USERNAME=유저이름
DB_PASSWORD=유저암호
DB_DATABASE=DB이름

 

실행 환경에 따라 local, dev, prod ... 이렇게 나누어 줄 수 있겠지만 우선은 연결 확인만 하는 단계이니 파일 이름은 .env로 정했습니다.

 

이렇게 만든 .env 파일이 실수로 저장소에 올라가는 일이 없도록 ignore 파일에도 명시해줍니다.

 

// .gitignore
# environment files
*.env

 

이제 app.module.ts에도 환경 변수를 불러올 수 있게 되었습니다.

 

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

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { ArticleModule } from './article/article.module';
import { LuckyModule } from './lucky/lucky.module';
import { User } from './user/entities/user.entity';
import { Article } from './article/entities/article.entity';
import { Lucky } from './lucky/entities/lucky.entity';

@Module({
  imports: [
    UserModule,
    ArticleModule,
    LuckyModule,
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_DATABASE,
      entities: [User, Article, Lucky],
      synchronize: false,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

ConfigModule을 통해 .env 파일을 불러와서 전역에 설정해주었습니다.

 

port는 number 자료형이라 +를 붙여서 형변환을 해주었습니다. 굳이 (number)로 해 줄 필요는 없겠다는 생각이었습니다.

 

synchronize는 true로 되어있었는데, false를 권장한다는 공식 문서에 따라 false로 변경하였습니다.

 

 

이제 정말 마지막 단계입니다. package.json에서 서버를 실행할때 어떤 모드로 실행할지 지정해줍니다. 팀원들은 모두 Mac OS 사용자라 괜찮겠지만, 저는 Windows OS를 사용하기 때문에 운영체제에 구애받지 않고 실행할 수 있도록 cross-env를 사용해줍니다.

 

// 기존 실행 스크립트
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    
// 변경된 실행 스크립트
    "start:dev": "cross-env NODE_ENV=dev nest start --watch",
    "start:debug": "cross-env NODE_ENV=debug nest start --debug --watch",
    "start:prod": "cross-env NODE_ENV=prod node dist/main",

 

 

이제 명령어로 서버를 실행시켜보면 정상적으로 구동되는 것을 확인할 수 있습니다!

 

반응형

'이론 > Backend' 카테고리의 다른 글

Nginx 리버스 프록시로 Mixed Content 우회하기  (0) 2023.03.12

댓글