NestJS 기초 (4) 데이터베이스 연동

2022. 10. 1. 22:38·개발/NestJS

Nest에는 SQL과 NoSQL 데이터베이스를 자유롭게 연결할 수 있습니다. 일반적으로 Nest에 데이터베이스를 연결하는 과정은 Express나 Fastify에 데이터베이스를 연결하기 위해 적절한 Node.js 설정을 하는 것과 동일합니다.

 

Nest는 TypeORM(@nestjs/typeorm)과 Sequelize(@nestjs/sequelize), Mongoose(@nestjs/mongoose)를 제공합니다. 이를 통해 모델/리포지토리 인젝션, 테스트, 비동기 설정을 보다 간편하게 진행할 수 있습니다.

TypeORM 사용하기

SQL 또는 NoSQL 데이터베이스 조합을 위해 Nest는 @nestjs/typeorm 패키지를 제공합니다. TypeORM은 타입스크립트를 위한 가장 완성도 높은 ORM으로 간주되고 있습니다.

 

이를 시작하려면 필수 디펜던시를 먼저 설치해야 합니다. 여기서는 MySQL을 사용할 것이지만 PostgreSQL, Oracle, SQLite, MongoDB 등을 사용할 수도 있습니다.

 

TypeORM 설치 명령어는 다음과 같습니다.

$ npm install --save @nestjs/typeorm typeorm mysql2

설치가 완료되면 루트 appModule에 TypeOrmModule을 임포트할 수 있습니다.

// app.module.ts

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

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [],
      synchronize: true,
    }),
  ],
})
export class AppModule {}

이 때 프로덕션 환경에서는 synchronize: true를 비활성해야 합니다. 그렇지 않으면 프로덕션 데이터가 유실될 수 있습니다.

저는 연결 과정에서 아래와 같은 오류가 발생했는데요.

ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)...

다음과 같이 해결했습니다. 먼저, mysql에 접속하여 다음을 입력하여 cashing_sha2_password 속성을 변경해줍니다.

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

모든 호스트에 대해 열어주려면 다음과 같이 입력합니다.

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

비밀번호 조건을 만족하지 않는다면 강력한 비밀번호를 설정하거나 또는 개발 환경에서 비밀번호 보안 강도를 낮출 수 있습니다.

set global validate_password.policy=LOW;

그리고 나서 이렇게 쳐보면 변경 사항을 확인할 수 있습니다.

SELECT Host,User,plugin,authentication_string FROM mysql.user;

연동은 됐으나 외부 ip에서 접속이 안될 때는 다음과 같이 권한을 설정해줍니다(도커 기준)

$ docker exec -it <mysql_container_id> bash
$ mysql -u root -p
$ CREATE USER 'root'@'%' IDENTIFIED BY 'password';
$ GRANT ALL ON *.* TO 'root'@'%';

여기까지 설정이 완료됐다면 이제 TypeORM DataSource와 EntityManager 객체를 프로젝트에서 사용할 수 있습니다.

// app.module.ts

import { DataSource } from 'typeorm';

@Module({
  imports: [TypeOrmModule.forRoot(), UsersModule],
})
export class AppModule {
  constructor(private dataSource: DataSource) {}
}

리포지토리 패턴

TypeORM은 리포지토리(repository) 디자인 패턴을 지원하며 각 엔티티(entity)는 고유한 리포지토리를 가질 수 있습니다. 이러한 리포지토리는 데이터베이스 데이터 소스로부터 얻을 수 있습니다.

 

시작하려면 엔티티가 필요합니다. User 엔티티를 정의해보도록 하겠습니다.

// user.entity.ts

import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm';

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

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

User 엔티티 파일은 users 디렉토리 내에 있습니다. 해당 디렉토리에는 UserModule과 연관된 모든 파일이 있습니다. 모델 파일을 저장할 곳을 지정할 수 있지만 모듈 디렉토리 내 해당 도메인 근처에 두는 게 좋습니다.

 

User 엔티티를 시작하려면 모듈 forRoot() 메소드 옵션 내 entities 배열에 이를 명시해줘야 합니다.

// app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [User],
      synchronize: true,
    }),
  ],
})
export class AppModule {}

이제 UserModule을 살펴보겠습니다.

// users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}

이 모듈은 forFeature() 메소드를 사용하여 현재 스코프에 등록된 레포지토리를 정의하고 있습니다. 여기서 우리는 @InjectRepository() 데코레이터를 사용하여 UsersRepository를 UsersService에 삽입할 수 있습니다.

// users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: string): Promise<User> {
    return this.usersRepository.findOneBy({ id });
  }

  async remove(id: string): Promise<void> {
    await this.usersRepository.delete(id);
  }
}

모듈 외부에서 리포지토리를 사용하고자 한다면 TypeOrmModule.forFeature를 해야 하므로, 다음과 같이 프로바이더를 엑스포트할 수 있습니다.

// users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  exports: [TypeOrmModule]
})
export class UsersModule {}

이제 UserHttpModule에 UsersModule을 임포트 하려면 @InjectRepository(User)를 사용할 수 있습니다.

// users-http.module.ts

import { Module } from '@nestjs/common';
import { UsersModule } from './users.module';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';

@Module({
  imports: [UsersModule],
  providers: [UsersService],
  controllers: [UsersController]
})
export class UserHttpModule {}

MongoDB 연결하기

MongoDB를 연결하고자 한다면 @nestjs/mongoose 패키지를 사용할 수 있습니다.

$ npm i @nestjs/mongoose mongoose

이후 다음과 같이 AppModule에 MongooseModule을 삽입해줍니다.

// app.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}

몽구스의 경우에는 모든 것이 스키마(Schema)에서 파생됩니다. 각 스키마는 MongoDB 컬렉션에 매핑되며, 컬렉션 내 도큐먼트 구조를 정의합니다. 스미카는 모델을 정의하는 데 사용됩니다. 모델은 해당 MongoDB 데이터베이스에서 도큐먼트를 생성 및 읽어오는 역할을 합니다.

 

위와 같이 데이터베이스를 연결했지만 데이터베이스 위치는 환경 변수를 통해 관리하는 것이 좋습니다. .env 파일을 생성한 다음 해당 URI를 불러와서 사용합니다.

 

먼저 npm i --save @nestjs/config 명령어를 통해 필요한 패키지를 설치해줍니다. 그리고 모듈 부분을 다음과 같이 수정해줍니다.

// app.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot(process.env.MONGODB_URI)],
})
export class AppModule {}

다음으로 Schema() 데코레이터를 사용하여 스키마를 생성할 수 있습니다.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type CatDocument = Cat & Document;

@Schema()
export class Cat {
  @Prop()
  name: string;

  @Prop()
  age: number;

  @Prop()
  breed: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);

@Shcema() 데코레이터는 스미카를 정의하는 클래스 역할을 합니다. 이는 우리의 Cat 클래스를 MongoDB 컬렉션에 연결해줍니다. MongoDB 컬렉션을 열어보면 "s"가 추가되어 컬렉션이 생성된 것을 알 수 있습니다.

 

@Prop() 데코레이터는 도큐먼트의 속성을 정의합니다. 예를 들어, 위의 스키마에서 우리는 name, age, breed를 정의하고 있습니다. 타입스크립트 메타데이터를 통해 각 속성들은 자동으로 해당 스키마 유형을 참조하고 있습니다. 그러나 배열과 같이 보다 복잡한 자료 구조의 경우에는 타입을 별도로 명시해줘야 합니다.

@Prop([String])
tags: string[];

또한 해당 속성이 필수적인지 아닌지를 설정할 수 있습니다.

@Prop({ required: true })
name: string;

이제 CatsModule을 살펴보도록 하겠습니다.

//cats.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat, CatSchema } from './schemas/cat.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [MongooseModule],
})
export class CatsModule {}

MongooseModule을 forFeature() 메소드를 제공하며 이를 통해 모듈을 구성할 수 있습니다. 또한 현재 스코프 내에서 어떤 모델이 등록되어야 하는지 정의합니다. 이 모델을 다른 모델에서 사용하고자 한다면 exports에 MongooseModule을 추가하고 다른 모듈에서 CatsModule을 불러오면 됩니다.

 

이렇게 스키마까지 등록을 완료했다면 Cat 모델을 CatsService에 @InjectModel() 데코레이터를 사용하여 삽입할 수 있습니다.

// cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

  async create(createCatDto: CreateCatDto): Promise<Cat> {
    const createdCat = new this.catModel(createCatDto);
    return createdCat.save();
  }

  async findAll(): Promise<Cat[]> {
    return this.catModel.find().exec();
  }
}

마지막으로 MongoDB 쿼리를 터미널에 띄워주려면 다음과 같이 작성할 수 있습니다.

// app.module.ts

import { LoggerMiddleware } from './common/logger.middleware';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import * as mongoose from 'mongoose';

@Module({
  imports: [
    ConfigModule.forRoot(),
    MongooseModule.forRoot(process.env.MONGODB_URI),
  ],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {
  configure() {
    mongoose.set('debug', true);
  }
}

참고 자료

  • https://docs.nestjs.com/techniques/database
  • https://docs.nestjs.com/techniques/mongodb

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

NestJS 기초 (5) 프로바이더  (0) 2022.10.04
NestJS 기초 (3) 컨트롤러  (0) 2022.09.30
NestJS 기초 (2) 프로젝트 시작하기  (0) 2022.09.29
'개발/NestJS' 카테고리의 다른 글
  • NestJS 기초 (6) 모듈
  • NestJS 기초 (5) 프로바이더
  • NestJS 기초 (3) 컨트롤러
  • NestJS 기초 (2) 프로젝트 시작하기
휘Hwi
휘Hwi
여행, 사진, 개발, 책 이야기를 기록하는 여행자 휘의 블로그 𓂍
Klook.com
  • 휘Hwi
    휘: 끝나지 않은 이야기
    휘Hwi
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 기록
        • 일상 에세이
        • 인사이트
        • 투자와 재테크
        • 코인 이야기
        • 아카이빙
        • 집무실 레터
        • 사랑에 대하여
        • 번역 이야기(完)
        • 프리랜서 일지(完)
      • 여행
        • 🌎 세계 여행기 S1 (完)
        • 🌊 삼삼한 여행기 (完)
        • 🚶 온더로드
        • 🇯🇵 일본
        • 🏝️ 발리
        • 🇻🇳 베트남
        • 🇱🇰 스리랑카
        • 🇮🇳 인도
        • 🇹🇭 태국
        • 🇸🇬 싱가포르
        • 🇦🇺 호주
        • 🇭🇰 홍콩
        • 🇰🇷 한국
        • 🍚 여행자의 한 끼
        • ℹ️ 여행 정보
      • 사진
        • 사진가
        • 사진 이론과 생각
        • 사진 관련 정보
      • 영상
        • 파이널컷 모션 공부
        • 고프로 GoPro
        • 영상 관련 정보
      • 책
        • 책 읽고 쓰기
      • 개발
        • 티스토리
        • Internet
        • HTML
        • CSS
        • JavaScript
        • Typescript
        • React
        • Node.js
        • Express
        • NestJS
        • Python
        • Django
        • MySQL
        • MongoDB
        • AWS
        • Deployment
        • Terminal
        • Git
        • Glossaries
        • Articles
        • Projects
        • TIL;
      • 미분류
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
휘Hwi
NestJS 기초 (4) 데이터베이스 연동
상단으로

티스토리툴바