십요일, Nest.js 전파하기 (1)

October 12, 2022 - 우원

십요일 주제 선정

회사에서는 십요일이라는 활동을 하고 있다. 일년에 10일은 회사에 기여할 수 있는 것들을 찾아보고 개인적으로 공부나 개발을 진행할 수 있는 시간이다.

십요일을 진행하는 날은 직접 리더에게 보고하고 출근 여부로 자유롭게 선택할 수 있다.

나는 이번에 십요일 주제를 "Nest.js"로 선정했다.

변화의 이유

기존 프레임워크의 변화를 주는 시도들이 내가 입사할 때부터 존재했었고 좀 더 가벼운 Node.js 기반 웹 프레임워크를 운영하는 것이 앞으로의 개발에도 긍정적으로 작용할 것이라고 생각했다.

하지만 막상 십요일을 준비하면서 기존 .NET MVC를 대체할만한 가치가 있는지 그리고 성능 면에서 앞서가는 부분이 있는지에 대해 스스로에게 명확한 답을 내어놓지 못했다. (계속해서 추가하도록 하겠다.)

그렇지만 고민을 해본 결과는 이렇다.

  1. 현재는 많은 크로스 플랫폼을 지원하고 있지만 여전한 .NET의 폐쇄성이 존재한다.
  2. C#은 Visual Studio에 한정되어 있다.(=Visual Studio에 최적화 되어있다.)
  3. 한국에서의 C#의 수요는 적은 편에 속한다.
  4. VSC와 같은 가벼운 코드편집기로 빠른 개발을 진행할 수 있는 프레임워크의 도입이 필요하다.
  5. Javascript 개발자 풀과 커뮤니티가 압도적이다.
  6. Node.js 어플리케이션은 빠르고 적은 비용으로 구축할 수 있다.
  7. non-blocking I/O 작업의 즉각적인 처리로 코드 실행 속도가 빨라져 더 나은 로드 관리가 가능하다.

Nest.js + GraphQL

Nest.js는 자바 스프링 프레임워크를 벤치마킹했다. 그렇기 때문에 프레임워크의 아키텍처도 스프링과 많이 유사하다.

spring

나는 자바를 써보지는 않았지만 Nest.js를 사용하면서 자바 스프링 프레임워크의 구조도 유추해 볼 수 있었다.

RESTful한 API를 구축하는 MVC 서버는 반드시 컨트롤러를 수반한다. 명칭에서도 정의가 포함되어 있듯이 외부의 요청을 알맞은 서비스에 연결하기 위해서는 없어서는 안된다.

물론 Nest.js도 컨트롤러가 존재한다. 하지만 이것은 REST API 서버를 구축할 때이다.

GraphQL을 활용해 자원을 가져올 경우에는 Nest.js는 리졸버(Resolver)를 사용한다.

spring

Express

  • 최신 Ecma Script 지원
  • Typescript (선택사항이나 사용 추세가 계속 늘어나고 있음)
  • CORS
  • HTTP 헤더 보안 (Express는 helmet을 사용)
  • Configuration
  • Interceptor
  • Middleware
  • Scheduling
  • Logging
  • Testing
  • Swagger 문서화
  • ORM

Node.js 기반 웹 프레임워크가 갖춰야 할 필수 기능은 위와 같다. Express의 경우 위의 기능들을 직접 구성하는데 많은 시간을 소비할 수도 있다.

하지만 Nest.js는 백엔드 서버가 갖추어야 하는 많은 필수 기능을 프레임워크 내에 내장하고 있고 추가로 필요한 기능을 설치하고 적용하는 방법을 문서로 제공한다.

spring

typescript(@type), eslint, prettier, jest, supertest... 기본적인 프레임워크 구축에 필요한 기능 및 라이브러리가 이미 설치되어 있다.

빠른 개발을 추구하고 일관된 코드 컨밴션을 구축하기를 원한다면 Express 보다는 Nest.js가 좀 더 적합하다.

Nest.js 시작하기

Nest CLI 설치하기

npm install -g @nestjs/cli

이로써 "nest" 커맨드를 사용할 수 있다.

nest를 입력해 보자

spring

name부터 간단히 살펴 보자. 대표적으로 "appication"과 같이 Nest.js 프로젝트를 생성하는 것 외에도 class, controller, decorator도 커맨드라인으로 생성 할 수 있다.

Nest 어플리케이션 생성하기

나는 VSC의 터미널에서 작업을 주로 진행했다.

"nest new"를 입력해 프로젝트를 생성한다.

nest new or nest new "project name"

패키지 매니저의 설정은 npm, yarn, pnpm 세 가지 중에 선호하는 것을 선택한다.

- src
		- app.controller.spec.ts
		- app.controller.ts
		- app.module.ts
		- app.service.ts
		- main
	- test
		- app.e2e-spec.ts
		- jest-e2e.json
	- .eslintrc.js
	- .gitignore
    - .prettierrc
    - nest-cli.json
	- package-lock.json
	- package.json
	- README.md
	- tsconfig.build.json
	- tsconfig.json

프로젝트를 살펴보면 위와 같은 플레이트가 이미 생성되어 있다.

(Git 초기화 같은 경우는 개인적으로 필요에 따라 진행하길 바란다.)

이제 프로젝트를 실행해보자!

npm run start:dev

// src/main.ts
  import { NestFactory } from '@nestjs/core';
  import { AppModule } from './app.module';

  async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    await app.listen(3000);
  }
  bootstrap();

Nest.js가 빌드되고 안정적으로 실행이 되면 브라우저로 화면이 자동으로 연결되지 않는다.

먼저 src 퐅더의 main.ts로 가서 포트의 번호를 확인하고 브라우저로 접속한다. (기본 3000)

spring

Hello World!를 확인 할 수 있다.

// src/app.service.ts
  import { Injectable } from '@nestjs/common';

  @Injectable()
  export class AppService {
    getHello(): string {
      return 'Hello World!';
    }
  }

"Hello World!"라는 문자열은 src/app.service.ts에서 확인 할 수 있다.

// src/app.contoller.ts
  import { Controller, Get } from '@nestjs/common';
  import { AppService } from './app.service';

  @Controller()
  export class AppController {
    constructor(private readonly appService: AppService) {}

    @Get()
    getHello(): string {
      return this.appService.getHello();
    }
  }

app.contoller.ts를 확인해보자 컨트롤러는 Get 요청을 받아 Service를 통해 Database에 저장된 데이터를 반환 받는다.

서비스 객체의 getHello()라는 메서드를 사용하는 것을 확인 할 수 있다.

GraphQL Config

이제 실제로 Apollo GraphQL 적용하고 Playground를 실행해보도록 하겠다.

npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express

현재 과정은 따로 Fastify를 고려하지 않고 디폴트인 Express 기반 Nest.js로 진행한다.

Nest.js는 GraphQL 어플리케이션을 구축하는데 두 가지 방법을 제공한다. Code First 방식과 Schema First 방식이 있다. 해당 과정에서는 Code First 방식을 채택했다.

먼저 app.service.ts, app.controller.ts, app.controller.spec.ts를 삭제한다. 그리고 app.module.ts를 @Module 데코레이터의 imports 옵션을 설정한다.

// src/app.module.ts
  import { Module } from '@nestjs/common';
  import { GraphQLModule } from '@nestjs/graphql';
  import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

  @Module({
    imports: [
      GraphQLModule.forRoot<ApolloDriverConfig>({
        driver: ApolloDriver, // 아폴로 드라이버 옵션
        autoSchemaFile: true, // Schema 파일 생성 여부 옵션
      }),
    ],
    controllers: [],
    providers: [],
  })
  export class AppModule {}

GraphQL Generate Module

앞서 GraphQL은 리졸버가 있어야한다고 언급했다. 그렇기 때문에 이를 위한 모듈을 하나 생성한다.

nest g mo challenge

// src/app.module.ts
  import { Module } from '@nestjs/common';
  import { GraphQLModule } from '@nestjs/graphql';
  import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
  import { ChallengeModule } from './challenge/challenge.module';

  @Module({
    imports: [
      GraphQLModule.forRoot<ApolloDriverConfig>({
        driver: ApolloDriver,
        autoSchemaFile: true,
      }),
      ChallengeModule,
    ],
    controllers: [],
    providers: [],
  })
  export class AppModule {}

app.module.ts을 확인하면 nest가 알아서 ChallengeModule을 임포팅한것을 확인 할 수 있다.

이제 리졸버를 생성해 보도록하자.

네이밍 규칙 : 이름.resolver.ts

src 폴더에 challenge.resolver.ts를 생성했다면 이제 파일로 이동해서 데코레이터로 리졸버라는 명찰(?)을 달아 주어야한다.

// challenge/challenge.resolver.ts
  import { Resolver,Query } from "@nestjs/graphql";

  @Resolver()
  export class ChallengeResolver{}

위의 코드를 작성하고 서버를 실행시켜 본다면 반드시 GraphQLError를 마주하게 된다.

GraphQLError: Query root type must be provided.

해당 리졸버에 쿼리문이 없어서 발생한 오류다. 간단한 쿼리를 작성해서 오류를 제거하도록 한다.

// challenge/challenge.resolver.ts
  import { Resolver,Query } from "@nestjs/graphql";

  @Resolver()
  export class ChallengeResolver{

      @Query(() => Boolean)
      iLoveYou(){
          return true
      }
  }

이제 서버가 오류 없이 동작한다.

현재의 과정은 nestjs 프로젝트를 빠르게 구축하고 graphql playground에 접속하는 것이 목표이기 때문에 자세한 설명은 추후에 진행하도록 한다.

GraphQL Playground

이제 localhost:3000/graphql으로 접속한다.

spring

GraphQL Playground에서는 쿼리문을 활용해 연결된 DB에서 CRUD 작업을 해볼 수 있다.

DB연결은 2장에서 PostgreSQL와 연동해서 로컬상에 실제 데이터를 생성하고 조회도 해보겠다.

오른쪽 끝의 책갈피를 한번 살펴보자

Graph Playground Docs

Docs는 Query, Mutation, Subscription 등의 타입에 따라 명칭과 반환값이 명료하게 표현된 것을 확인 할 수 있다.

그리고 해당 쿼리에 인자가 존재한다면 추가로 인자값의 구성과 타입도 확인할 수 있다.

이제 Mutation을 임시로 만들어서 확인해보도록 하자.

// challenge/challenge.resolver.ts
  import { Resolver,Query, Mutation } from "@nestjs/graphql";

  @Resolver()
  export class ChallengeResolver{

      @Query(() => Boolean)
      iLoveYou(){
          return true
      }

      @Mutation(() => String)
      loveChange(myLoveStatus : boolean){
          return !myLoveStatus
      }
  }

다시 Playground로 가서 새로고침을 해보자. 데코레이터에 따라 Query와 Mutation으로 구분된 것을 확인 할 수 있다.

spring

Graph Playground Schema

아래의 Schema를 확인해보자.

type Query {
    iLoveYou: Boolean!
  }

  type Mutation {
    loveChange: String!
  }

위와 같이 타입으로 감싼 형태를 확인 할 수 있다.

스키마의 경우는 서버 실행시 root에 자동으로 생성되는 schema 파일에서 확인 할 수 있지만, 앞서 설정한 autoSchemaFile: true 옵션으로 인해 생성이 되지 않고 graphql이 알아서 기억하고 있다.

Query와 Mutation으로 분류된 리스트 형식으로 빠르게 인자들의 값까지 확인하고 싶다면 Docs를 통해 확인하고 그 외에 쿼리문을 Playground에 어떻게 작성해야하는지 확인 할 때는 Schema를 확인하도록 한다.

다음장으로 넘어가도록 한다.

logo

우원 /

안녕하세요👏
우원입니다.
Email
Gihub
안녕하세요. 우원봇입니다.
logo