IT Study/JavaScript

[NestJS] 게시글 DB에 저장하기 (with PostgreSQL)

짹짹체유 2024. 1. 4. 20:46

 

Nest.JS를 처음으로 학습하고자, 코치님께 인프런 무료 강의를 추천받았다

강의평도 좋고 설명도 잘 되어있어서 초보자가 듣기에 매우 좋은 것 같다

다만, 한 가지 문제가 발생했다.

강의가 2021년에 게시되어서 그 사이에 TypeORM 부분의 버전이 달라졌다 !

강의에서 에러가 나는 부분은 구글링을 통해서 해결을 해야했다

 

우선 실습 중인 프로젝트는 '게시글 CRUD 구현'이다.

 

기존 강의 실습 코드

// board.repository.ts
import { Board } from './board.entity';
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {}
  • 문제가 되는 부분이 바로 repository의 EntityRepository 모듈이다.
  • 아래 사진처럼 코드를 작성하면 VScode에서 취소선이 그어진다.

 

※ Repository
: 엔티티 개체와 함께 작동하며 엔티티 찾기, 삽입, 업데이트, 삭제 등을 처리
DB에 관련된 일은 서비스에서 하는 것이 아닌 Repository에서 함 ⇒ Repository Pattern이라고도 부름.

< Req → Controller → Service → Repository → Service → Controller → Res >

 

@EntityRepository() : 클래스를 사용자 정의 저장소로 선언하는데 사용. 사용자 지정 저장소는 일부 특정 엔티티를 관리하거나 일반 저장소일 수 있음.

 

TypeORM 0.3.0 부터는 @EntityRepository가 deprecated 되어 사용하지 않는다.

 

 

해결 방법으로는 2가지가 있다고 한다.

  1. Typeorm 버전 downgrade 하여 사용하기
    -> repository 패턴을 사용
  2. Typeorm 버전에 맞게 code 수정하기
    -> repository 패턴 사용 방식
    -> repository 패턴 미사용 방식

Typeorm을 계속 사용할 것이라면 버전을 downgrade하기보다는 현재 버전에 맞게 연습하는 것이 좋은 것 같아서

2번 방식으로 아래에서 해결을 시도했다.

 

// boards.service.ts
@Injectable()
export class BoardsService {
  constructor(
    @InjectRepository(BoardRepository)
    private boardRepository: BoardRepository,
  ) {}

  async createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
    const { title, description } = createBoardDto;
    const board = this.boardRepository.create({
      title,
      description,
      status: BoardStatus.PUBLIC,
    });
    await this.boardRepository.save(board);
  }

  async getBoardById(id: number): Promise<Board> {
    const found = await this.boardRepository.findOne(id);

    if (found) {
      throw new NotFoundException(`Can't find Board with id ${id}`);
    }
    return found;
  }
 }

 


에러 해결

1차 시도

TypeORM 0.3.0 부터는 @EntityRepository를 사용하는 대신 DataSource의 createEntityMangager()를 이용한다.

DataSource: 데이터베이스와의 상호 작용은 데이터소스를 설정한 후에만 가능하다.

TypeORM의 DataSource는 데이터베이스 연결 설정을 보관하고, 사용하는 RDBMS에 따라 초기 데이터베이스를 연결 또는 연결 풀을 설정한다.

첫 번째 인수: target이 될 Entity, 두 번째 인수: EntityManager 타입의 manager(entity에 대한 operation을 수행할 수 있는 것)를 넣어줌

 

// board.repository.ts
import { Injectable } from '@nestjs/common';
import { Board } from './board.entity';
import { DataSource, Repository } from 'typeorm';

@Injectable()
export class BoardRepository extends Repository<Board> {
  constructor(dataSource: DataSource) {
    super(Board, dataSource.createEntityManager());
  }
  async getBoardById(id: number) {
    return await this.findOneBy({ id: id });
  }
}
  • constructor로 DataSource를 설정하고 super로 첫 번째 인수에는 엔티티인 Board를 넣고 두 번째 인수에는 datasource.createEntityManager()를 넣어주었다. (다른 블로그 참고)

 

🚨 에러 발생

createBoard의 Promise<Board> 부분에서 "A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value."

 

에러 발생 원인: return board를 안 해줘서 에러 발생. Promise<Board>면 Board 형태의 값을 return 한다는 의미인데 내 코드에는 return이 없었다.

에러 해결: return board 추가

 

 

🚨 에러 발생

getBoardById의 findOne(id) 부분에서 "Type 'number' has no properties in common with type 'FindOneOptions<Board>'."

에러 발생 원인: TypeORM의 버전이 올라가면서 findOne은 사용하지 않는 것 같았다.

에러 해결: findOne을 findOneBy로 변경하고 소괄호 안에는 id를 중괄호로 감싸서 넣어줌 findOneBy({id})

 


여기서부터가 EntityRepository를 사용하기 않음으로써 발생한 에러 해결을 위해 시도한 과정들이다.

 

🚨 에러 발생

ERROR [ExceptionsHandler] No metadata for "BoardRepository" was found.
EntityMetadataNotFoundError: No metadata for "BoardRepository" was found. at DataSource.getMetadata.

 


2차 시도

Copilot에게 물어봤다.

From Copilot. "board.entity.ts 파일의 export 위에 @Entity()를 추가해라"

 

🚨 동일 에러 발생

=> 실패

 


3차 시도

1) service 파일의 constructor(@InjectRepository(BoardRepository))를 constructor(@InjectRepository(Board))로 수정

2) module 파일에서 imports: [TypeOrmModule.forFeatrue([BoardRepository])]를 [TypeOrmModule.forFeatrue([Board])]로 수정

 

//// 기존 코드
// board.service.ts
@Injectable()
export class BoardsService {
  constructor(
    @InjectRepository(BoardRepository)
    private boardRepository: BoardRepository,
  ) {}
  ...
}

// board.repository.ts
export class BoardRepository extends Repository<Board> {
  constructor(@InjectRepository(BoardRepository) private dataSource: DataSource) {
    super(Board, dataSource.manager);
  }
  ..
}


//// 수정 코드
// board.service.ts
@Injectable()
export class BoardsService {
  constructor(
    @InjectRepository(Board)
    private boardRepository: BoardRepository,
  ) {}
  ...
}

// board.repository.ts
export class BoardRepository extends Repository<Board> {
  constructor(@InjectRepository(Board) private dataSource: DataSource) {
    super(Board, dataSource.manager);
  }
  ..
}

 

=> 실패

 

🚨 에러 발생

 

ERROR [ExceptionHandler] dataSource.createEntityManager is not a function
TypeError: dataSource.createEntityManager is not a function

 

에러 발생 원인: dataSource.createEntityManager가 함수가 아니라며 에러 발생

에러 해결: createEntityManager와 동일한 Type인 manager가 있다고 해서 dataSource.manager로 수정

 


4차 시도

🚨 에러 발생

ERROR [ExceptionsHandler] No metadata for "Board" was found.
EntityMetadataNotFoundError: No metadata for "Board" was found.

 

결국 처음과 동일한 문제가 다시 발생했고, BoardRepository에서 Board로만 수정된 것...

( 절망편...... )

 

 


5차 시도

지금까지의 코드에 처음에 Copilot 알려준 board.entity.ts 파일의 export 위에 @Entity() 추가

// board.entity.ts
@Entity()
export class Board extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  status: BoardStatus;
}

 

=> 성공

 

 

Postman으로 결과 확인

 

 

 

 

 

 

 

 

참고자료

https://velog.io/@sheoae12/NestJS-Custom-Repository-%EB%A7%8C%EB%93%A4%EA%B8%B0

https://velog.io/@ansunny1170/No-metadata-for-BoardRepository-was-found

 

 

반응형