make it simple
article thumbnail

나오게 된 배경

  • 자바스크립트가 발전하면서 다양한 framework가 나오게되면서  front-end/back-end 둘 다 다양하게 활용할 수 있게 되었다. front-end는 Angular,React,Vue 등 다양한 프레임워크들이 빠르고 유연하고 확장성있게 개발할 수 있도록 지원한다. back-end도 express.js,node.js 프레임워크, 또한 다양한 라이브러리들을 지원한다. 하지만, 유연성있는 아키텍쳐를 설계할 수 있는것은 없다. 그런 문제점을 해결하기 위해 나온게 NestJS이다. NestJS의 가장 큰 장점은 아키텍쳐를 유연성있고, 유지보수가 쉬우며, 테스트하기 쉽고 결합도 루즈하게 개발할 수 있게 만들어준다. 간단한 게시판 API를 만들면서 느껴보자.

NestJS 설치하기 

  • 밑에 명령어 터미널에 입력
 npm i -g @nestjs/cli
  • 설치 후 밑 명령어로 버전확인
nest --version
  • nest 프로젝트 생성
nest new {프로젝트명}

생성된 프로젝트 초기 구조

  • controller: 프레젠테이션 계층입니다. request(요청) & response(응답) 을 처리합니다.
  • service: 서비스 계층입니다. 비즈니스 로직을 처리하는 곳입니다.
  • module: 모듈 메타 데이터입니다. java의 main method 처럼 nest가 어플리케이션을 시작할 때 사용되는 메인 모듈입니다. 안에있는 모듈들이 실행됩니다.


모듈 생성

nest g res {모듈명}
  • test라는 모듈을 생성했다. 놀랍게도!!! 위에 명령어만 쳤는데 밑처럼 자동으로 해당 모듈 관련 dto,entities,controller,service,module 이 생성된다.

npm run start
  • 위 명령어를 사용하여 서버를 킨 후 http://localhost:3000/boards 를 입력한 후 접속하면 밑에 이미지 처럼 표시된다. (이 말은 기본적인 라우팅은 되있고 데이터베이스와 연동 후 응답값만 처리해주면 된다.)

/boards
/boards/{id}


데이터베이스 연동(MYSQL)

  • 밑에 명령어로 DB 연결할 라이브러리(객체 관계 ORM 매퍼) 설치
yarn add mysql typeorm @nestjs/typeorm
  • /entities 안에 board.entity.ts 파일안에 엔티티 내용 작성
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity("board")
export class Board {
    @PrimaryGeneratedColumn()
    private id: number;

    @Column({ length: 1000})
    private title: string;

    @Column({length: 5000})
    private content: string;


    constructor(
        title: string,
        content: string
    ){
        this.title = title;
        this.content = content;
        return this;
    }
}
  • app.module.ts 안에  import { TypeOrmModule } from '@nestjs/typeorm';@module 안에 TypeOrmModule.forRoot() 안에 DB 접속정보를 입력한후 엔티티 생성한 모듈을 넣어주면된다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardsModule } from './boards/boards.module';

@Module({
  imports: [TypeOrmModule.forRoot({
    "type": "mysql",
    "host": "hostname",
    "port": 3306,
    "username": "username",
    "password": "password",
    "database": "database",
    "entities": ["dist/**/**.entity{.ts,.js}"],
    "synchronize": true
  }), BoardsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • npm run start 로 서버를 구동시킨후 DB 툴을 보면 테이블/컬럼이 생성되었다. 


Repository 연동

  • /db 폴더 생성
    • /db 경로에 typeorm-custom.module.ts 파일 생성 후 밑 코드 작성
import { DynamicModule, Provider } from '@nestjs/common';
import { getDataSourceToken } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { TYPEORM_CUSTOM_REPOSITORY } from './typeorm.decorator';

export class TypeOrmCustomModule {
  public static forCustomRepository<T extends new (...args: any[]) => any>(
    repositories: T[],
  ): DynamicModule {
    const providers: Provider[] = [];

    for (const repository of repositories) {
      const entity = Reflect.getMetadata(TYPEORM_CUSTOM_REPOSITORY, repository);

      if (!entity) {
        continue;
      }

      providers.push({
        inject: [getDataSourceToken()],
        provide: repository,
        useFactory: (dataSource: DataSource): typeof repository => {
          const baseRepository = dataSource.getRepository<any>(entity);
          return new repository(
            baseRepository.target,
            baseRepository.manager,
            baseRepository.queryRunner,
          );
        },
      });
    }

    return {
      exports: providers,
      module: TypeOrmCustomModule,
      providers,
    };
  }
}
  • /db - typeorm.decorator.ts 파일 생성 후 밑 코드 작성 
import { SetMetadata } from '@nestjs/common';

export const TYPEORM_CUSTOM_REPOSITORY = 'TYPEORM_CUSTOM_REPOSITORY';

// eslint-disable-next-line @typescript-eslint/ban-types
export function CustomRepository(entity: Function): ClassDecorator {
  return SetMetadata(TYPEORM_CUSTOM_REPOSITORY, entity);
}
  • /src/boards - boards.repository.ts 파일 생성 후 위에 만든 @CustomRepository 사용
import { CustomRepository } from "src/db/typeorm.decorator";
import { Repository } from "typeorm";
import { Board } from "./entities/board.entity";

@CustomRepository(Board)
export class BoardRepository extends Repository<Board>{};
  • /boards - boards.module.ts
import { Module } from '@nestjs/common';
import { BoardsService } from './boards.service';
import { BoardsController } from './boards.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Board } from './entities/board.entity';
import { BoardRepository } from './boards.repository';
import { TypeOrmCustomModule } from 'src/db/typeorm-custom.module';
@Module({
  imports:[TypeOrmCustomModule.forCustomRepository([BoardRepository])],
  controllers: [BoardsController],
  providers: [BoardsService]
})
export class BoardsModule {}

게시판 생성 API

  • /boards -  boards.service.ts
import { Injectable } from '@nestjs/common';
import { BoardRepository } from './boards.repository';
import { CreateBoardDto } from './dto/create-board.dto';
import { UpdateBoardDto } from './dto/update-board.dto';
import { Board } from './entities/board.entity';

@Injectable()
export class BoardsService {
  
  // repository 주입
  constructor(private readonly boardRepository: BoardRepository) {}

  // 생성 
  async create(createBoardDto: CreateBoardDto) {
    const board = new Board(createBoardDto.title,createBoardDto.content);
    return this.boardRepository.save(board);
  }

  // 리스트 조회
  findAll() {
    return this.boardRepository.find();
  }

  // 단일 조회
  findOne(id: number) {
    return this.boardRepository.findOneBy({ id: id});
  }

  update(id: number, updateBoardDto: UpdateBoardDto) {
    return `This action updates a #${id} board`;
  }

  remove(id: number) {
    return `This action removes a #${id} board`;
  }
}
  • /boards/dtocreate-board.dto.ts 에 밑처럼 필드값 작성
export class CreateBoardDto {
    title:string;
    content:string;
}

위처럼 하면 게시글 리스트 조회/단일 조회/생성 API가 생성되었다.


테스트하기

  • POST http://localhost:3000/boards - 생성 API (성공)

  • GET http://localhost:3000/boards - 리스트 조회 API (성공)

  • GET http://localhost:3000/boards/2 - 아이디 값으로 조회 API (성공)


결론

  • 회사에서 새 프로젝트로 NestJS를 사용하기 위해간단한 CRUD를 구현해봤다. 필자는 Spring + JPA 프레임워크를 쓴 경험이 있는데 NestJS + TypeORM도 유사한거 같다. 개인적으로 왜 핫한지 알꺼 같다! 모듈식으로 구조가 정해져 있어서 유지보수 측면에서 좋을꺼 같다. 또한, 간단하고 빠르게 설정부터 개발까지 할 수 있다. 모듈만 생성하면 알아서 관련 controller/service/dto/entity 까지 만들어진다.하지만 아직까진 Spring + JPA 와 차별점과 장점은 잘 모르겠다. 계속 해보면서 알아가야겠다. 
profile

make it simple

@keep it simple

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!