Tech Blog of Pinomaker
article thumbnail

0. 들어가기 전에

NodeJS + Express를 이용하여 백엔드를 구축하면 일반적으로는 Func을 기반으로 라우터와 컨트롤러로 구성하는 계층 구조를 많이 볼 수 있다. 하지만 Express에서도 Spring boot나 NestJS와 같이 controller를 구성하여 controller-service-repository 계층 구조로 구성하고 싶은 개발자가 많을 거다.

 

Class를 기반으로 Express를 Controller, Service, Repository로 계층을 구성하게 되면 비교적 Spring boot나 NestJS 개발자들도 쉽게 접근할 수도 있다는 장점이 있다. 이번에는 routing-controllers와 typedi를 이용하여 Express로 해당 계층을 구현할 예정이다.

 

(1) Routing Controllers

Routing Controller는 Express.js나 koa.js에서 HTTP Request를 처리하는 Controller 생성을 돕는 라이브러리다. 사용하는 방법은 NestJS, Spring boot의 Controller와 다르지 않다.

 

 

routing-controllers

Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript.. Latest version: 0.10.4, last published: 5 months ago. Start using routing-controllers in your project by runni

www.npmjs.com

 

(2) Typedi

Tpyedi는 JS, TS용의 의존성 주입을 위한 패키지다. Spring Container와 같이 컨테이너로 의존성 주입을 도와주는 데 사용할 수 있다.

 

 

 

typedi

Dependency injection for TypeScript.. Latest version: 0.10.0, last published: 3 years ago. Start using typedi in your project by running `npm i typedi`. There are 667 other projects in the npm registry using typedi.

www.npmjs.com

 

 

1. 프로젝트 생성하기

먼저 yarn을 이용하여 Node.js 프로젝트를 생성하자. 이 부분은 자세한 설명은 하지 않는다. Typescript를 기준으로 해당 게시글을 작성할 것이다.

 

yarn init -y

 

그 후에는 필요한 패키지를 먼저 설치하자.

 

yarn add express

yarn add -D @types/express typescript @types/express @types/node nodemon ts-node

 

그리고 나서는 tsconfig.json까지 빠르게 생성한다. 

npx tsc --init
# tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "target": "es2017",
    "lib": ["es2017", "esnext.asynciterable"],
    "typeRoots": ["node_modules/@types"],
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "module": "commonjs",
    "pretty": true,
    "sourceMap": true,
    "declaration": true,
    "outDir": "dist",
    "allowJs": true,
    "noEmit": false,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "importHelpers": true,
    "baseUrl": "src"
  },
  "include": ["src/**/*.ts", "src/**/*.json", ".env"],
  "exclude": ["node_modules", "src/http", "src/logs", "src/tests"]
}

 

 

그리고 root 디렉토리에 src를 만들고 그 아래에 server.ts와 app.ts를 각각 구성한다.

 

├── src
│   ├── app.ts
│   └── server.ts
│       └── BaseTime.Entity.ts
├── tsconfig.json
├── package.json

 

// ** app.ts

// ** Module Imports
import express from "express";

export class App {
  public app: express.Application;

  constructor() {
    this.app = express();
  }

  /**
   * Express를 시작한다.
   * @param port 포트
   */
  public async createExpressServer(port: number): Promise<void> {
    this.app.listen(port, () => {
      console.log(`Server is running on PORT : ${port}`);
    });
  }
}

 

// ** Server.ts

import { App } from "./app";

try {
  const app = new App();
  const port = 3000;

  app.createExpressServer(port);
} catch (error) {
  console.error(error);
}

 

이렇게 구성했다면 이제 실행을 위한 nodemon.json 설정과 packange.json에 실행 스크립트를 작성하고 실행해보자.

 

먼저 root 디렉토리에 nodemon.json 파일을 생성 후 아래와 같이 작성한다.

// ** nodemon.json
{
  "watch": ["./src"],
  "ext": "ts",
  "ignore": ["./test/*"],
  "exec": "ts-node --transpile-only src/server.ts"
}

 

그 후에 package.json에 실행 스크립트를 아래와 같이 추가하면 실행을 할 수 있다.

  "scripts": {
    "build": "tsc",
    "dev": "nodemon"
  },

 

이제 스크립트에 입력한 dev 명령어를 입력하면 서버가 켜지는 것을 확인할 수 있다.

yarn dev

 

 

2. Routing Controller 설정

DI를 하기 전에 먼저 Http Request를 Controller로 잡기 위하여 Routing Controller 설정부터 시작하자. 먼저 패키지를 설치해준다.

yarn add routing-controllers class-transformer class-validator cors reflect-metadata

yarn add -D tslib @types/cors

 

자 이제 src 내에 utils와 controller 폴더를 만들고 그 안에 각각 routingConfig.ts와 app.controller.ts를 생성해보자.

 

├── app.ts
├── controller
│   └── app.controller.ts
├── server.ts
└── utils
    └── routingConfig.ts

 

먼저 routingConfig 파일에 Routing Controller 관련 설정을 작성한다.

// ** src/utils/routingConfig.ts

export const routingControllerOptions = {
  cors: true,
  routePrefix: "/", // ** Base url
  controllers: [`${__dirname}/../controller/*{.ts,.js}`], // ** Controller Sacn
  middlewares: [`${__dirname}/../middleware/*{.ts,.js}`], // ** Middleware Scan
  defaultErrorHandler: false,
};

 

그리고 app.controller.ts를 아래와 같이 구성한다.

// ** Module Imports
import { JsonController } from "routing-controllers";

@JsonController()
export default class AppController {
  constructor() {}
}

 

자 그리고 app.ts에 Routing Controller를 위한 설정을 작성하면 된다. Routing Controller에 대한 설정과 더불어 기본적인 middleware를 추가했다.

 

import "reflect-metadata";

// ** Module Imports
import express from "express";
import { useExpressServer } from "routing-controllers";
import { routingControllerOptions } from "./utils/routingConfig";
import cors from "cors";

export class App {
  public app: express.Application;

  constructor() {
    this.app = express();
    this.setMiddlewares();
  }

  /**
   * 미들웨어를 세팅한다.
   */
  private setMiddlewares(): void {
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: false }));
    this.app.use(cors());
  }

  /**
   * Express를 시작한다.
   * @param port 포트
   */
  public async createExpressServer(port: number): Promise<void> {
    useExpressServer(this.app, routingControllerOptions); // ** Routing Controller Config
    this.app.listen(port, () => {
      console.log(`Server is running on PORT : ${port}`);
    });
  }
}

 

이제 라우팅 처리를 위해 다시 app.controller로 돌아가서 라우팅 처리를 해보자

// ** Module Imports
import { Get, JsonController } from "routing-controllers";

@JsonController()
export default class AppController {
  constructor() {}

  @Get("/") // ** Get : "/" 으로 들어온 요청 잡음
  public hello() {
    return "Hello world";
  }
}

 

자 이제 Routing Controller에 대한 설정이 끝났다. 이제 브라우저에 localhost:3000으로 들어가면 이렇게 노출되는 것을 확인할 수 있다.

 

 

길고 길었지만 현재 코드를 보면 Spring boot나 NestJS에서 라우팅 처리를 하는 Controller와 별반 다르지 않아보인다. 공식 문서에 가면 더 다양한 메서드에 대한 정보가 나오기에 확인 해보자.

profile

Tech Blog of Pinomaker

@pinomaker

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