NestJS + PostgreSQL + TypeORM 시작하기

SH·2022년 8월 17일

사전 설치

PostgreSQL 설치

참고: https://dog-developers.tistory.com/122?category=896103

팝업 차단되어있는 경우엔 웹사이트에서 자동으로 설치가 되지 않을 수도 있다


Failed to load SQL Modules into database Cluster 에러 발생시

참고: https://ch-phillip.tistory.com/16


Boilerplate 제작

yarn으로 시작하기

# yarn 설치
npm i yarn -g

# Nest 전역 설치
npm i -g @nestjs/cli

# Nest 프로젝트 생성
nest new project_name

# 이후에 어떤 패키지를 선택할 것인지 터미널에서 물어본다 yarn 선택하면 됨

# nest 실행하기
yarn start

참고: https://velog.io/@easyhwan/Nest-Yarn%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Nest-%EC%B4%88%EA%B8%B0%EC%85%8B%ED%8C%85


config 설정으로 PostgreSQL & TypeORM 사용 (feat. env and @nest/config)

app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: () => ({
        type: 'postgres',
        host: process.env.POSTGRES_HOST,
        port: Number(process.env.POSTGRES_PORT),
        username: process.env.POSTGRES_USER,
        password: String(process.env.POSTGRES_PASSWORD),
        database: process.env.POSTGRES_DB,
        entities: [join(__dirname, '**', '*.entity.{ts,js}')],
        synchronize: true,
      }),
    }),
    UsersModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

.env 파일에 각각의 POSTGRES_HOST 등에 해당하는 변수를 지정해주면 된다.

주의) POSTGRES_PASSWORD는 문자열이여야 하므로 숫자로 지정한 경우 문자열 형변환을 해주어야 한다

ormconfig.json 파일을 루트 디렉토리에 지정하고 여기에 설정 파일을 적는 방법도 있다. 그런데 나는 계속 missingDriver 에러가 나서 그냥 이렇게 처리했음


CRUD 구현 - res 명령어

nest g res users

user 객체를 CRUD할 시 필요한 템플릿(dto, controller 등)이 자동으로 생성됨


yarn start 하고
http://localhost:3000/users
http://localhost:3000/users/1
접속하면 serice에서 return하고 있는 stirng 값이 떠야 정상

+)

@nestjs/mapped-types 없어서 에러나면
yarn add @nestjs/mapped-types
추가하면 됨


핵심 용어

service - 핵심 비즈니스 로직을 처리하는 부분

controller - 라우팅 및 파라미터나 path variable을 걸러서 service에 넘겨주는 기능(이지만 이번 게시글에는 파라미터 처리도 service에서 해주었다)

module - 해당 폴더 안의 모듈을 관리하는 클래스

provider - dependency로서 주입될 수 있는 것
ex) controller가 service를 사용할 때 provider로 service가 됨

nodejs + express 환경에서는 controller 폴더 안에 UserController, PostController가 있고 service 폴더 안에 UserService, PostController가 있는 식으로 만들었다. nestjs에서 res 명령어를 사용해 템플릿을 쓰는 경우에는 모듈 별로 폴더링이 된다.


CRUD 구현 - Create 구현

res 명령어로 template 생성한 상태에서

  1. Entity 만들기
  2. Service 만들기
  3. 해당 Entity.module.ts 파일에 import

  1. UserEntity 만들기
import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import * as argon2 from "argon2";

@Entity("user") // user 테이블과 매핑
export class UserEntity {

  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 32 })
  username: string;

  @Column()
  password: string;

  @BeforeInsert()
  async hashPassWord() {
    this.password = await argon2.hash(this.password);
  }
  
}

@Entity: 괄호 안에 넣은 문자열이 테이블명이 됨

@PrimaryGeneratedColumn(): 해당 어노테이션이 붙은 필드가 기본키가 됨. 괄호 안에 설정을 안넣으면 id는 auto increment로 지정됨

@BeforeInsert: DB에 Insert하기 전에 수행되도록 함

@Column: 이게 붙으면 테이블 필드로 만들어짐


  1. UserService.ts 만들기
@Injectable() // 생성자 주입 시 이게 붙어있으면 이게 주입됨 
export class UsersService {

  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}

  async create(createUserDto: CreateUserDto) {
    
    const { username, password } = createUserDto;
    
    const newUser = new UserEntity();

    newUser.password = password;
    newUser.username = username;

	// 검증하는 부분. createDto에서 @IsNotEmpty()와 같은 검증 어노테이션과 함께 사용됨. 
    const validator_error = await validate(newUser);
    if (validator_error.length > 0) {
      const _error = { username: 'UserInput is not valid check type'};
      throw new HttpException(
        { message: 'Input data validation failed', _error },
        HttpStatus.BAD_REQUEST,
      );
    } else {
      return await this.userRepository.save(newUser);
    }


  }
 
  ...
 
 }

service는 repository를 의존한다. 즉, UserService는 UserRepository에 있는 메소드를 사용해야 한다. 이 때 new UserRepository()로 새 객체를 만들어서 사용하지 않고 생성자 주입을 사용한다


constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}

TypeORM은 Repository 디자인 패턴을 지원한다. 따라서 Entity마다 Repository가 있기 때문에 Service 클래스에 생성자에서 @InjectRepository 데코레이터를 사용하면 사용할 Entity의 Repository를 외부에서 주입시킬 수 있다.

+) Notice the use of the private syntax. This shorthand allows us to both declare and initialize the catsService member immediately in the same location.

출처: nestjs 공식문서


@Injectable() // 생성자 주입 시 이게 붙어있으면 이게 주입됨 
export class UsersService {
	...
}

또한 UserService도 UserController에서 사용된다. controller도 service를 의존하기 때문에, UserService 클래스에 @Injectable을 써 줘서 UserController가 사용할 수 있도록 한다


  1. user.module.ts 파일에 import 해줌
@Module({
  imports: [
    TypeOrmModule.forFeature(
      [UserEntity]
    )
  ],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

app.module.ts에서는 template 자동 생성할 때 자동으로 추가되므로 굳이 할 필요는 없다.


왜 모듈 단에서 @Module 어노테이션에 저렇게 값을 넘겨줘야 됨??
해당 모듈의 controller가 무엇인지, 모듈에서 provider로 사용되는 애는 누구인지 명시해줘야지 나중에 nestjs 엔진이 DI를 해줄 수 있음

+) 이와 같이 module단에서 dependency를 정의해주는 것을 Provider registration이라고 함

Now that we have defined a provider (CatsService), and we have a consumer of that service (CatsController), we need to register the service with Nest so that it can perform the injection. We do this by editing our module file (app.module.ts) and adding the service to the providers array of the @Module() decorator.

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

출처: nestjs 공식문서


test는 postman을 사용해서 하면 된다

npm run start

참고: https://velog.io/@qnfmtm666/2.-NestJS-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%97%B0%EA%B2%B0-Postgresql%EA%B3%BC-User-%EB%AA%A8%EB%93%88


삽질

ERROR [ExceptionHandler] No repository for "UserRepository" was found. Looks like this entity is not registered in current "default" connection?
RepositoryNotFoundError: No repository for "UserRepository" was found. Looks like this entity is not registered in current "default" connection?
    at RepositoryNotFoundError.TypeORMError [as constructor] (C:\Users\LSH\source\nest-template\nestjs-template\src\error\TypeORMError.ts:7:9)

[Nest] 23524  - 2022. 08. 17. 오전 1:10:41   ERROR [TypeOrmModule] Unable to connect to the database. Retrying (5)...
C:\Users\LSH\source\nest-template\nestjs-template\src\users\entities\user.entity.ts:1
import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1032:15)

참고: https://stackoverflow.com/questions/59435293/typeorm-entity-in-nestjs-cannot-use-import-statement-outside-a-module

profile
블로그 정리안하는 J개발자

1개의 댓글

comment-user-thumbnail
2022년 10월 21일

혹시 삽질이라고 적으신 부분 첫번째 에러 어떻게 해결하셨나요?

답글 달기