본 포스팅은 Nest.js
를 처음 접하는 개발자가 Typeorm
의 repository 패턴
을 적용하며 마주한 문제를 해결 하고 있습니다.
repository 패턴
을 사용하는 방식
& 사용 안하는 방식
이 있습니다.실습 참고 콘텐츠 : Youtube 따라하면서 배우는 NestJS
- Typeorm Docs repository란?
- Google 검색 repository 패턴이란?
- [ NestJS ] Repository Pattern에 대하여 알아보자
- 문제 보완하기 (3) - 타입스크립트 Repository, DTO, generic
DAO(Data Access Object) 정도로 이해 하시면 됩니다.
Nest.js 튜토리얼을 따라 실습 하던 중에 아래와 같은 error가 발생 했습니다.
repository 패턴을 적용하려하자 문제가 나왔습니다.
[Nest] 11926 - 02/08/2023, 6:24:48 PM ERROR [ExceptionsHandler] No metadata for "BoardRepository" was found.
EntityMetadataNotFoundError: No metadata for "BoardRepository" was found.
at DataSource.getMetadata (/home/kyc/study/nestjs-board-app/src/data-source/DataSource.ts:438:30)
at Repository.get metadata [as metadata] (/home/kyc/study/nestjs-board-app/src/repository/Repository.ts:53:40)
at Repository.create (/home/kyc/study/nestjs-board-app/src/repository/Repository.ts:130:18)
at BoardsService.createBoard (/home/kyc/study/nestjs-board-app/src/boards/boards.service.ts:19:44)
at BoardsController.createBoard (/home/kyc/study/nestjs-board-app/src/boards/boards.controller.ts:18:35)
at /home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/router/router-execution-context.js:38:29
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at /home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/router/router-execution-context.js:46:28
at /home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/router/router-proxy.js:9:17
튜토리얼은 되는데 나는 왜 안되냐? 라고 묻는다면 원인은
Typeorm package version
입니다.
해결하는 방법은 2가지입니다.
- Typeorm 버전 downgrade 하여 사용하기
-> 그리고 repository 패턴을 사용합니다.- Typeorm 버전에 맞게 code 수정하기
-> repository 패턴 사용 방식
-> repository 패턴 미사용 방식
service.ts에서 create board 되는 것을 확인한 상태입니다. 이제 여기서 repository 패턴으로 넘어가야 합니다.
넘어가는 방법은 verison을 downgrade하는 방식을 참조하시기 바랍니다.
board.repository.ts
import { DataSource, Repository } from "typeorm";
import { Board } from "./board.entity";
@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {
}
boards.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { BoardStatus } from './board-status.enum'
import { CreateBoardDto } from './dto/create-board.dto';
import { BoardRepository } from './board.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { Board } from './board.entity';
@Injectable()
export class BoardsService {
constructor(
@InjectRepository(BoardRepository)
private boardRepository: BoardRepository,
) {}
createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
const { title, description } = createBoardDto;
const board = this.boardRepository.create({
title,
description,
status: BoardStatus.PUBLIC
});
await this.boardRepository.save(board)
return board;
}
}
boards.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BoardRepository } from './board.repository';
import { BoardsController } from './boards.controller';
import { BoardsService } from './boards.service';
@Module({
imports: [
TypeOrmModule.forFeature([BoardRepository])
],
controllers: [BoardsController],
providers: [BoardsService]
})
export class BoardsModule {}
제가 실습에 사용하던 버전은
"@nestjs/typeorm": "^9.0.1",
"typeorm": "^0.3.12",
튜토리얼에서 사용하는 버전은
"@nestjs/typeorm": "^8.0.1",
"typeorm": "^0.2.34",
튜토리얼 혹은 아래 사이트의 글을 참조하여 version을 낮춥니다.
버전 관련 비슷한 사례
- 인프런 지식공유 참여
@nestjs/typeorm': '^8.0.3'
에서@nestjs/typeorm': '^8.0.1'
로 변경하니 가능했다는 내용.- stack overflow - 댓글 읽기
역시 버전을 변경하니 가능하다는 내용.
**.repository.ts
을 사용해 @InjectRepository(~~)
의 ~~
부분에 class Repository
를 주입하는 방식입니다.
board.repository.ts
import { DataSource, Repository } from "typeorm";
import { Board } from "./board.entity";
@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {
const { title, description } = createBoardDto;
const board = this.boardRepository.create({
title,
description,
status: BoardStatus.PUBLIC
});
await this.boardRepository.save(board)
return board;
}
}
boards.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { BoardStatus } from './board-status.enum'
import { CreateBoardDto } from './dto/create-board.dto';
import { BoardRepository } from './board.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { Board } from './board.entity';
@Injectable()
export class BoardsService {
constructor(
@InjectRepository(BoardRepository)
private boardRepository: BoardRepository,
) {}
createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
return this.boardRepository.createBoard(createBoardDto);
}
boards.module.ts
# 수정 없습니다.
아래 사이트를 참고하여 수정하면 됩니다.
Nestjs 공식 사이트 - entity
이렇게 하면 service 단에서 DB 접근을 직접 하기 때문에 비즈니스 로직에 집중하기 어렵고 코드도 많이 길어지는 단점이 있습니다.
boards.service.ts 수정 후
import { BoardRepository } from "typeorm"; // 그대로 사용
import { Board } from "./board.entity"; // 추가
constructor(
// @InjectRepository(BoardRepository) // 삭제
@InjectRepository(Board)
private boardRepository: BoardRepository, // 여기서 사용
) {}
boards.module.ts 수정 후
// import { BoardRepository } from './board.repository'; // 삭제
import { Board } from './board.entity'; // 추가
@Module({
imports: [
// TypeOrmModule.forFeature([BoardRepository]) // 삭제
TypeOrmModule.forFeature([Board]) // 추가
],
controllers: [BoardsController],
providers: [BoardsService]
})
버전이 변경 된 후, 많은 혼란한 상황을 함께 겪은 듯한 동지애(?)를 느꼈습니다. 하지만 정말 아쉽게도 제가 사용하는 버전에 완벽하게 적용하지는 못했습니다.
그래도 많은 도움을 받아 소개합니다.
board.repository.ts 수정 후
import { InjectRepository } from "@nestjs/typeorm";
import { DataSource, Repository } from "typeorm";
import { BoardStatus } from "./board-status.enum";
import { Board } from "./board.entity";
import { CreateBoardDto } from "./dto/create-board.dto";
export class BoardRepository extends Repository<Board> {
// constructor 추가
constructor(@InjectRepository(Board) private dataSource: DataSource) {
super(Board, dataSource.createEntityManager())
}
async createBoard(createBoardDto: CreateBoardDto) : Promise<Board> {
const { title, description } = createBoardDto;
const board = this.create({
title,
description,
status: BoardStatus.PUBLIC
});
await this.save(board)
return board;
}
}
boards.service.ts 수정 후
import { Injectable, NotFoundException } from '@nestjs/common';
import { BoardStatus } from './board-status.enum'
import { CreateBoardDto } from './dto/create-board.dto';
import { BoardRepository } from './board.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { Board } from './board.entity';
@Injectable()
export class BoardsService {
constructor(
// @InjectRepository(Board) // 삭제
// @InjectRepository(BoardRepository) // 삭제
private boardRepository: BoardRepository,
) {}
createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
return this.boardRepository.createBoard(createBoardDto);
}
boards.module.ts 수정 후
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Board } from './board.entity';
import { BoardRepository } from './board.repository'; // 추가
import { BoardsController } from './boards.controller';
import { BoardsService } from './boards.service';
@Module({
imports: [
TypeOrmModule.forFeature([Board])
],
controllers: [BoardsController],
providers: [BoardsService, BoardRepository] // BoardRepository 추가
})
export class BoardsModule {}
발생했었던 문제
[Nest] 5032 - 02/09/2023, 6:43:34 PM ERROR [ExceptionHandler] dataSource.createEntityManager is not a function
TypeError: dataSource.createEntityManager is not a function
at new BoardRepository (/home/kyc/study/nestjs-board-app/src/boards/board.repository.ts:13:33)
at Injector.instantiateClass (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/injector.js:348:19)
at callback (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/injector.js:54:45)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at Injector.resolveConstructorParams (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/injector.js:133:24)
at Injector.loadInstance (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/injector.js:58:13)
at Injector.loadProvider (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/injector.js:85:9)
at /home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/instance-loader.js:49:13
at async Promise.all (index 4)
at InstanceLoader.createInstancesOfProviders (/home/kyc/study/nestjs-board-app/node_modules/@nestjs/core/injector/instance-loader.js:48:9)
위 첫 번째 방법과 다른 부분은 repository.ts 파일입니다.
board.repository.ts 수정 후
import { InjectRepository } from "@nestjs/typeorm";
import { DataSource, Repository } from "typeorm";
import { BoardStatus } from "./board-status.enum";
import { Board } from "./board.entity";
import { CreateBoardDto } from "./dto/create-board.dto";
export class BoardRepository extends Repository<Board> {
constructor(@InjectRepository(Board) private dataSource: DataSource) {
super(Board, dataSource.manager) // 변경
// super(Board, dataSource.createEntityManager()) // 삭제
}
async createBoard(createBoardDto: CreateBoardDto) : Promise<Board> {
const { title, description } = createBoardDto;
const board = this.create({
title,
description,
status: BoardStatus.PUBLIC
});
await this.save(board)
return board;
}
}
답답해서 DataSource
class를 뒤적거리다가 createEntityManager
와 동일한 Type의 manager
를 발견했습니다. 적용했더니.. 다행히도 정상 작동됨을 확인했습니다...
board.repository.ts
import { EntityRepository, Repository } from "typeorm";
import { Board } from "./board.entity";
@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {
}
어쩐지.. 튜토리얼 과는 다르게
EntityRepository이렇게사용안함
으로 되어있던데... 느낌이 싸했습니다.
(친절히 알려주고 있었는데 가뿐히 무시한 대가는 소중한 시간이었습니다. ㅠㅠ)
https://docs.nestjs.com/techniques/database#repository-pattern
https://docs.nestjs.com/recipes/sql-typeorm
분명 Repository pattern이라고 안내하고 있는데, 눈을 씻고 찾아봐도 repository.ts
파일에 대한 설명이 안보입니다.. ㅠㅠㅠ
제 생각에 service.ts
에서 사진과 같이 usersRepository
를 선언 할 때, Repository<User>
형식으로 선언하면 이게 과연 repository pattern인가... 에서 뇌정지가 왔습니다. 제가 repository pattern을 제대로 이해 못한 거겠죠?...😂
privaite usersRepository: Repository<User>
이어서 공식 문서를 보던 중에 트랜젝션을 적용하는 방법도 있었습니다.
추후 프로젝트를 진행할 떄 트랜젝션 적용은 필수 요소입니다. 나중에.. 나중에 필요할 때 꺼내 봐야 겠습니다.
https://docs.nestjs.com/techniques/database#typeorm-transactions
끗! 감사합니다.
좋은 글 너무 감사드립니다 ㅠㅠ