Node, Nest, Deno/🦁 Nest - Series

Nest + gql + typeORM(@nestjs/typeorm) (3) Repository pattern, @InjectRepository

DarrenKwonDev 2020. 11. 21. 22:07

쉽게 말해서, entity를 다른 곳에서 사용했던 것처럼 불쑥 import해서 사용할 수 없고, @InjectRepository를 이용하여 불러온 후 사용해야 한다는 것입니다.

 

 

docs.nestjs.com/techniques/database#repository-pattern

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

TypeORM supports the repository design pattern, so each entity has its own repository. These repositories can be obtained from the database connection.

 

공식 문서의 예제를 가져와서 보겠습니다. 우선 아래처럼 Data Mapping 형태로 Entity를 정의합시다. Data Mapping 형태로 Entity는 다른 말로 "repository"라고 합니다. nest에서는 repository사용하고 있으니 참고합시다.

 

active record와 data mapper에 대한 설명은 제가 정리한 포스트  공식 문서를 참고하시면 좋습니다.

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

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

  @Column()
  firstName: string;

  @Column()
  lastName: string;

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

 

appModule 부분에서 entities를 할당합니다.

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 {}

 

user entity가 있는 곳의 userModule에 TypeOrmModule의 forFeature 메서드를 통해 User 엔티티를 import해줍시다.

그리고 당연히 이 userModule은 appModule에 넣어줘야겠죠. (nest의 기본. 모든 모듈은 appModule에 종속된다.)

forFeature는 통해 repository를 특정 scope에 등록합시다. 다른 말로는 repository를 특정 모듈에등록하는겁니다.

This module uses the forFeature() method to define which repositories are registered in the current scope. 

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 {}

 

위에서 repository를 모듈에 등록해뒀다면, 해당 Service provider에서 @InjectRepositoy를 통해 inject할 수 있게 됩니다.

we can inject the UsersRepository into the UsersService using the @InjectRepository() decorator:

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.findOne(id);
  }

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

 

If you want to use the repository outside of the module which imports TypeOrmModule.forFeature, you'll need to re-export the providers generated by it. You can do this by exporting the whole module, like this:

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 {}

 

 

정리하자면,

1. repository (Data Mapping 형식의 Entity)를 정의하고

2. 해당 repository를 사용하고자 하는 Module에 TypeOrmModule의 forFeature 메서드를 통해 import 하고

3. service에서 @InjectRepository()를 통해 사용하시면 됩니다.


 

자, 이해를 했으니 실제로 사용해봅시다. 

 

전 포스트에서 했던 것처럼 @Entity와 gql을 위한 @ObjectType을 작성함에 있어

다음과 같이 BaseEntity를 extends하지 않았으므로 data mapper의 방식으로 사용해야 합니다. 

import { Field, ObjectType } from '@nestjs/graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

// Active Record 방법으로 쓰려면 extends BaseEntity 해야 함.

@ObjectType() // nest
@Entity() // typeORM
export class Restaurant {
  @Field(() => Number) // nest(gql)
  @PrimaryGeneratedColumn() // typeORM
  id: number;

  @Field(() => String) // nest(gql)
  @Column() // typeORM
  name: string; // nest(typescript)

  @Field(() => Boolean)
  @Column()
  isVegan: boolean;

  @Field(() => String)
  @Column()
  address: string;

  @Field(() => String)
  @Column()
  ownersName: string;

  @Field(() => String)
  @Column()
  categoryName: string;
}

 

해당 Entity의 scope에 해당하는 모듈에 forFeature를 통해 entity를 넣고, 해당 모듈을 appModule에 넣습니다.(이 부분 코드는 생략합니다. 모들 모듈이 appModule에 모여야 하는 건 nest에 있어 기본이니까요)

 

또, Service를 provider에 넣어줍시다. 모듈 하에 있는 것들은 모듈로 응집하는 게 기본입니다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class RestaurantService {
  getAll() {
    ...중략
  }
}
@Module({
  imports: [TypeOrmModule.forFeature([Restaurant])],
  providers: [RestaurantResolver, RestaurantService],
})
export class RestaurantsModule {}

 

이제 Service에서 repository를 사용할 수 있습니다. 앞서 대강 작성한 Service를 제대로 작성해봅시다.

contructor 부분에 @InjectRepository를 통해 repository를 불러오고

repository를 이용해 fine, remove 등 메서드를 사용하는 로직을 작성하면 됩니다.

Service에 작성한 로직은 Controller나 resolver에게 붙이면 되겠죠?

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Restaurant } from './entities/restaurant.entity';

@Injectable()
export class RestaurantService {
  constructor(
    @InjectRepository(Restaurant)
    private readonly restaurant: Repository<Restaurant>,
  ) {}

  getAll(): Promise<Restaurant[]> {
    return this.restaurant.find();
  }
}

 

다음과 같이 Service를 불러와 로직에 사용하였습니다. 비즈니스 로직과 route를 처리하는 controller 혹은 gql을 사용할 때 이용하는 resolver는 분리하는 것이 nest의 원칙이니까요 ㅇㅇ.

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { CreateRestaurantDto } from './dtos/create-restaurant.dto';
import { Restaurant } from './entities/restaurant.entity';
import { RestaurantService } from './restaurants.service';

@Resolver((of) => Restaurant)
export class RestaurantResolver {
  constructor(private readonly restaurantService: RestaurantService) {}

  @Query(() => [Restaurant])
  restaurants(): Promise<Restaurant[]> {
    return this.restaurantService.getAll();
  }
}