kakasoo

Nestia 적용하기 본문

프로그래밍/NestJS

Nestia 적용하기

카카수(kakasoo) 2023. 2. 19. 23:21
반응형

이 글을 찾아본 것은 이미 Nestia를 알고 온 사람들일 거라 생각되기 때문에 간단한 설명만 적자면,

Nestia란?

Node.js, TypeScript 서버의 장점인 클라이언트와 동일한 언어 사용을 극대화하는 라이브러리라고 할 수 있다.

동일한 언어를 사용한다는 것은 서버 개발자가 미리 정의한 타입의 중복 개발을 막을 수 있다는 의미가 된다.

이로 인해 프론트 개발자는 이미 개발된 타입과 SDK를 사용해 좀 더 UI/UX 측면에 집중할 수 있게 된다.

프로젝트 생성

$ npm i -g @nestjs/cli
$ nest new project-name

프로젝트 생성은 동일하게 진행해주면 된다.

Nestia를 만드신 Samchon님의 레포지토리 예제 대부분은 본인 색채가 강하지만, 아래는 참고할 만 했다.

여기서는 Samchon님의 Backend 레포지토리를 fork하지 않고, 최초 nest 프로젝트 생성부터 시작한다.

nestia 적용

 

$ npx nestia setup

npx nestia setup을 입력하면 compiler, package manager, TS Config File을 묻는 질문이 나온다.

선택은 본인에 맞게 하면 되지만, 컴파일러의 경우, 모르겠다면 ts-patch를 쓰면 된다.

TS Config File은 tsconfig.json과 tsconfig.build.json 중 고르면 된다.

자동적으로 nestia 사용에 필요한 각종 모듈들이 설치되는데, typia, @nestia/core, @nestia/sdk, nestia,

이렇게 4개의 라이브러리가 설치된다.

 

typia는 Runtime-validator로, class-validator의 역할을 대신하며, 더 빠른 속도를 내주는 라이브러리다.

@nesita/core는 TypedRoute, TypedBody 등 Nestia의 핵심적인 코드들을 export해주며,

@Nestia/sdk는 앞서 말한 것처럼 타입이나 API 호출 함수를 내보낼 수 있게 SDK 형태로 만들어주는 기능이다.

추가로 swagger를 생성하는 것도 해준다.

nestia는 nestia github repository 설명에 따르면 단순한 cli라고 한다.

 

@nestia/core

import { TypedBody, TypedRoute } from '@nestia/core';
import { Controller } from '@nestjs/common';
import { AppService } from './app.service';

interface TestDto {
  /**
   * @format email
   */
  email: string;
}
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @TypedRoute.Post('nestia')
  getHello(@TypedBody() dto: TestDto): string {
    console.log('work...');
    return dto.email;
  }
}

당신이 NestJS를 안다는 전제 하에, Nest 서버를 만들면 자동적으로 AppController가 생기는 것을 알 것이다.

AppController에 자동으로 생기는 getHello 함수의 데코레이터들을 위와 같이 변경했다.

TypedRoute.Post()TypedBody() 이렇게 두 개다.

TypedRoute의 메서드들은 일반적인 Nest 메서드들보다 더 빠른 JSON.stringify 성능을 가지고 있다.

TypedBody는 마찬가지로 더 빠른, validate 기능을 수행한다.

놀라운 건 클래스가 아닌 인터페이스로도 검증이 가능하다는 점이다.

 

interface TestDto {
  /**
   * @format email
   */
  email: string;
}

Nestia의 validate 규칙은 주석의 설명을 따른다.

이 부분은 typia의 comment tags에 대한 설명을 읽어보면, 어떤 태그들이 가능한지 확인할 수 있다.

 

 

Runtime Validators

Super-fast Runtime validator (type checker) with only one line - samchon/typia

github.com

 

 

nestia.config.ts

import type SDK from '@nestia/sdk';

export const NESTIA_CONFIG: SDK.INestiaConfig = {
  /**
   * List of files or directories containing the NestJS controller classes.
   */
  input: 'src/controllers',

  /**
   * Output directory that SDK would be placed in.
   *
   * If not configured, you can't build the SDK library.
   */
  output: 'src/api',

  /**
   * Whether to assert parameter types or not.
   *
   * If you configure this property to be `true`, all of the function parameters would be
   * checked through the [typia](https://github.com/samchon/typia#runtime-type-checkers).
   * This option would make your SDK library slower, but would enahcne the type safety even
   * in the runtime level.
   *
   * @default false
   */
  // assert: true,

  /**
   * Whether to optimize JSON string conversion 2x faster or not.
   *
   * If you configure this property to be `true`, the SDK library would utilize the
   * [typia](https://github.com/samchon/typia#fastest-json-string-converter)
   * and the JSON string conversion speed really be 2x faster.
   *
   * @default false
   */
  // json: true,

  /**
   * Whether to wrap DTO by primitive type.
   *
   * If you don't configure this property as `false`, all of DTOs in the
   * SDK library would be automatically wrapped by {@link Primitive} type.
   *
   * For refenrece, if a DTO type be capsuled by the {@link Primitive} type,
   * all of methods in the DTO type would be automatically erased. Also, if
   * the DTO has a `toJSON()` method, the DTO type would be automatically
   * converted to return type of the `toJSON()` method.
   *
   * @default true
   */
  // primitive: false,

  /**
   * Building `swagger.json` is also possible.
   *
   * If not specified, you can't build the `swagger.json`.
   */
  swagger: {
    /**
     * Output path of the `swagger.json`.
     *
     * If you've configured only directory, the file name would be the `swagger.json`.
     * Otherwise you've configured the full path with file name and extension, the
     * `swagger.json` file would be renamed to it.
     */
    output: 'dist/swagger.json',
  },
};
export default NESTIA_CONFIG;

주석이 많아서 복잡한데, 주석을 모두 지우고 나면 별 거 없다.

input, output, 그리고 swagger의 output 이렇게 3가지만 정의하면 된다.

input은 컨트롤러가 모여 있는 폴더를 지정하고, output은 sdk가 나올 폴더를 지정하면 된다.

swagger.output도 마찬가지로, swagger.json이 생성될 위치를 지정하면 된다.

dist/swagger.json으로 설정한 이유는 프로젝트의 rootDir에 위치하게 만들기 위해 그렇게 한 것일 뿐이다.

 

@nestia/sdk - swagger

$ npx nestia swagger

이 명령어를 입력하면 swagger.json이 만들어진다.

 

import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { readFileSync } from 'fs';

import path from 'path';

export const SwaggerSetting = (app: INestApplication) => {
  // const config = new DocumentBuilder()
  //   .setTitle('server docs')
  //   .setDescription('The API description')
  //   .setVersion('1.0')
  //   .addTag('kakasoo')
  //   .addBearerAuth({ type: 'http', scheme: 'bearer', in: 'header' }, 'Bearer')
  //   .build();

  // const document = SwaggerModule.createDocument(app, config);
  // SwaggerModule.setup('api', app, document);

  const swaagerConfig = readFileSync(path.join(__dirname, '../../swagger.json'), 'utf8');
  SwaggerModule.setup('api', app, JSON.parse(swaagerConfig));
};

내 파일에서 dist/swagger.json을 가리키는 경로를 path.join으로 찾아준 다음 readFileSync로 읽어주었다.

순수한 string이기 때문에 이 swaggerConfig를 JSON.parse로 읽어, 자바스크립트 객체로 만들어주었다.

이를 SwaggerModule.setup에 넣어주기만 하면 nestjs 스웨거 문서 연동이 끝난다.

 

/**
 * it's very important API.
 *
 * how to use? read bellow!
 *
 * @param id maybe you know already
 * @returns true, just return true. that's all.
 */
@TypedRoute.Post(':id')
async nestiaTest(@TypedParam('id', 'number') id: number) {
  console.log('id : ', id);
  return true;
}

이해를 돕기 위해 이게 스웨거 상에 어떻게 표시되는지를 첨부한다.

 

 

스웨거 작성을 위한 document는 아래에서 확인할 수 있다.

 

SDK Generator

Superfast validation decorators for NestJS + Swagger/SDK generator - samchon/nestia

github.com

 

@nestia/sdk - sdk(api)

$ npx nestia sdk

이 명령을 입력하면 sdk가 만들어진다.

위에 nestia.config.ts에 정의한대로 생성되기 때문에,

나의 경우 dist/api/functional/nestia/index.ts에 API SDK가 생성된 것을 볼 수 있다.

api/functional 부터는 API의 경로를 표현한 것으로 보인다.

 

/**
 * @packageDocumentation
 * @module api.functional.nestia
 * @nestia Generated by Nestia - https://github.com/samchon/nestia 
 */
//================================================================
import { Fetcher, Primitive } from "@nestia/fetcher";
import type { IConnection } from "@nestia/fetcher";

/**
 * it's very important API.
 * 
 * how to use? read bellow!
 * 
 * @param connection connection Information of the remote HTTP(s) server with headers (+encryption password)
 * @param id maybe you know already
 * @returns true, just return true. that's all.
 * 
 * @controller NestiaController.nestiaTest()
 * @path POST /nestia/:id
 * @nestia Generated by Nestia - https://github.com/samchon/nestia
 */
export function nestiaTest
    (
        connection: IConnection,
        id: number
    ): Promise<nestiaTest.Output>
{
    return Fetcher.fetch
    (
        connection,
        nestiaTest.ENCRYPTED,
        nestiaTest.METHOD,
        nestiaTest.path(id)
    );
}
export namespace nestiaTest
{
    export type Output = Primitive<boolean>;

    export const METHOD = "POST" as const;
    export const PATH: string = "/nestia/:id";
    export const ENCRYPTED: Fetcher.IEncrypted = {
        request: false,
        response: false,
    };

    export function path(id: number): string
    {
        return `/nestia/${encodeURIComponent(id)}`;
    }
}

나의 경우 이런 형태로 생성되었는데, 사실 이게 어떻게 생긴 코드인지는 중요하지 않다.

어쨌거나 이 함수를 클라이언트가 가져가서 쓸 수 있다는 게 더 중요하다.

 

import { nestiaTest } from './dist/api/functional/nestia';

(async function () {
  const response = await nestiaTest({ host: 'http://127.0.0.1:3000' }, 1);
})();

테스트를 위해 서버 디렉토리에서 직접 실행해보니 제대로 서버에 요청이 들어갔음을 확인할 수 있다.

참고로 IConnection은 아래와 같다.

 

import { IEncryptionPassword } from "./IEncryptionPassword";
/**
 * Connection information.
 *
 * `IConnection` is a type of interface who represents connection information of the remote
 * HTTP server. You can target the remote HTTP server by wring the {@link IConnection.host}
 * variable down. Also, you can configure special header values by specializing the
 * {@link IConnection.headers} variable.
 *
 * If the remote HTTP server encrypts or decrypts its body data through the AES-128/256
 * algorithm, specify the {@link IConnection.encryption} with {@link IEncryptionPassword}
 * or {@link IEncryptionPassword.Closure} variable.
 *
 * @author Jenogho Nam - https://github.com/samchon
 */
export interface IConnection {
    /**
     * Host address of the remote HTTP server.
     */
    host: string;
    /**
     * Header values delivered to the remote HTTP server.
     */
    headers?: Record<string, string>;
    /**
     * Encryption password of its closure function.
     */
    encryption?: IEncryptionPassword | IEncryptionPassword.Closure;
}

IConnection을 제외하고는 우리가 만든 API를 포맷팅했기에, 파라미터가 거의 유사하다.

반응형