무스마 백엔드 살펴보기 - 개발환경 세팅부터 배포까지 (1/2)

글을 쓴 이유

저는 대학생 때부터 무스마에 입사하기 전까지 Java를 이용하여 주로 개발을 하였고 당연히 Java/Spring을 이용하는 회사에 입사를 할 줄 알았습니다. 감사하게도 언어는 도구일 뿐이라며 무스마 SW 개발팀에서 입사 제안을 해주셨고 작년 12월부터 Node.js 백엔드 개발자로 입사하여 재미있는 회사 생활을 하고 있습니다.

입사 이틀째에 설레는 마음으로 프로젝트를 VSCode에 불러왔을 때 정말 당황했었는데요. 자바스크립트와 Node.js 관련 지식이 전무했던 저는 수많은 파일 및 옵션들이 무엇을 의미하는지 정말 하~나도 몰랐고 개발부터 배포까지 어떻게 진행되는지 이해가 잘 가지 않았기 때문입니다.

그래서

  • Typescript, Node.js에 익숙하지 않고
  • 수많은 파일이 뭔지 하나도 모르겠지만
  • 대충이라도 알고 싶고 실습도 좀 해보고 싶어!

하는 분들 및 앞으로 합류하실 무스마 크루의 개발자분들을 위하여 작성하게 되었습니다.

모든 코드는 GitHub에서 확인하실 수 있습니다.

개발환경 세팅

패키지 설치

NVM 설치

NVM을 이용하면 Node.js 설치하면 다양한 버전을 설치 및 변경이 가능해집니다.

brew install nvm
mkdir ~/.nvm

vi ~/.zhrc # 아래 내용을 붙여넣는다.
 
export NVM_DIR="$HOME/.nvm"
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"  # This loads nvm
[ -s "/usr/local/opt/nvm/etc/bash_completion.d/nvm" ] && . "/usr/local/opt/nvm/etc/bash_completion.d/nvm"  # This loads nvm bash_completion

zsh compinit: insecure directories, run compaudit for list. 에러가 발생할 경우,

cd /usr/local/share/zsh

sudo chmod -R 755 ./site-functions

Node.js(LTS) 설치

nvm install 14 # LTS 버전 확인 후 입력

Yarn 설치

무스마에서는 Node Package Manager로 yarn을 사용하고 있습니다.

그 이유는

  • 패키지 다운로드 속도가 더 빠르고
  • 기능이 더 많기 때문에 (workspaces 등)
npm install -g yarn

Nest CLI 설치

Nest는 타입스크립트로 빌드되는 Node.js 서버측 애플리케이션을 구축하기 위한 프레임워크입니다.

CLI를 이용하여 빠르고 간편하게 프로젝트를 생성할 수 있으므로 설치합니다.

npm install -g @nestjs/cli

패키지 매니저로 yarn을 쓰면서 왜 npm으로 다운로드 하냐? 라는 의문을 가지실 수 있는데요.

yarn을 이용하여 @nestjs/cli를 글로벌로 다운로드 하면 생기는 이슈가 있습니다.

VS Code 설치

Jetbrains사의 IntelliJ, Webstrom 등을 사용하셔도 상관 없으나 무스마에서는 VS Code를 적극적으로 사용하고 있습니다.

brew install --cask visual-studio-code

Docker 설치

brew install --cask docker

Kubectl 설치

brew install kubectl

minikube 설치

무스마 SW 개발팀은 워크로드를 Amazon EKS에 배포합니다.

듀토리얼을 EKS로 진행하기에는 비용적으로, 시간적으로 무리가 있으므로 쿠버네티스를 로컬에서 실행할 수 있도록 minikube를 설치합니다.

brew install minikube

Lens 설치

쿠버네티스 클러스터를 제어하는 ​​데 필요한 유일한 IDE입니다.

minikube가 기본적으로 제공해주는 대시보드가 있으나 더 아름답고 사용하기 편하기에 사용하고있습니다.

brew install --cask lens

GitHub Personal access tokens 발급받기

여기를 참고하여 wrtie:packages 스코프를 선택한 뒤 Personal access tokens을 발급받습니다.

토큰은 도커 이미지 푸시, 풀을 위해 사용하는 GitHub Container Registry와 무스마에서 생성한 eslint를 다운받을 때 사용됩니다.

발급받은 토큰을 ~/.npmrc에 저장합니다.

vi ~/.npmrc
//npm.pkg.github.com/:_authToken=[GITHUB_PERSONAL_ACCESS_TOKENS]

VSCode Extension 설치

Extensions: Marketplace 에서 Extension을 설치합니다.

ESLint

eslint

Prettier - Code formatter

preetier

프로젝트 생성

개발환경 세팅이 끝났으니 본격적으로 시작해보겠습니다.

musma 프로젝트 생성

Nest CLI를 이용하여 musma 프로젝트를 생성합니다.

package manager로 yarn을 선택해주세요.

nest new musma

musma-eslint 적용

.npmrc 생성

프로젝트 루트에 .npmrc 파일을 생성합니다.

echo @musma:registry=https://npm.pkg.github.com/ >> .npmrc

.npmrc 파일을 생성함으로써 pakcage.json 파일의 dependency@musma/eslint-config와 같이 @musma가 붙는 패키지가 있다면,

공식 npm 저장소 http://registry.npmjs.org/ 대신에 GitHub Package Registry https://npm.pkg.github.com/에서 찾아서 다운로드 하게 됩니다.

읽어보세요

[무스마 기술블로그] GitHub 패키지 저장소 호스팅: GitHub Package Registry 시작하기

.eslintrc.js 변경 및 .prettierrc 제거

Nest CLI를 이용하면 ESLint를 구성할 수 있는 설정파일인 .eslintrc.js와 코드를 정렬해주는 Code Formatter인 Prettier를 적용할 수 있는 .prettierrc 파일을 생성해주는데요.

.prettierrc 설정은 다운로드 할 @musma/eslint-config에 들어있기 때문에 제거합니다.

rm .prettierrc

.eslintrc.js는 아래와 같이 변경합니다.

module.exports = {
  extends: [
    '@musma',
  ],
}

@musma/eslint-config에 적용된 세부적인 룰이 궁금하시다면 여기에서 확인하실 수 있습니다.

.eslintignore 생성

.eslintignore 파일을 생성하여 규칙 적용이 필요없는 node_modules, *.js, *d.ts 파일을 제외합니다.

vi .eslintignore
**/node_modules
**/*.js
**/*.d.ts

@musma/eslint-config 적용

@musma/eslint-config를 설치합니다.

yarn add @musma/eslint-config --dev --update-checksums

필요없는 의존성 제거

아래의 의존성들은 musma/eslint-config에 설치되어 있으므로 제거합니다.

  • @typescript-eslint/eslint-plugin
  • @typescript-eslint/parser
  • eslint
  • eslint-config-prettier
  • eslint-plugin-prettier
  • prettier
yarn remove @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-prettier prettier 

settings.json 생성

save 버튼을 누를 때 마다 prettier가 적용되도록 settings.json을 생성합니다.

mkdir .vscode
vi .vscode/settings.json
{
  "eslint.codeAction.showDocumentation": {
    "enable": true
  },
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "editor.formatOnSave": true,
  "eslint.alwaysShowStatus": true
}

ESLint, Prettier 테스트

접근제한자가 없으므로 app.controller.ts 파일이 오류가 나고 있을텐데요.

getHello 함수에 public을 붙여주고 저장을 눌러서 아래와 같이 변경되면 성공입니다.

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()
  }
}

Hello World! 테스트

서버를 실행시켜 Hello World!를 정상적으로 응답하는지 테스트해보겠습니다.

yarn start
curl -XGET 'localhost:3000'
Hello World!

기능 구현

app.controller.ts에 간단한 GET, POST 요청을 구현해보겠습니다.

타입 생성

파라미터 및 반환값으로 사용할 타입 두 가지를 생성합니다.

type ResponseType = {
  type: string
  data: string
}

type PostBodyType = {
  data: string
}

GET

GET 요청에 대한 getRequest 함수를 구현합니다.

@Get('/get')
public getRequest(): ResponseType {
  return { type: 'GET', data: 'getResponse' }
}

POST

POST 요청에 대한 postRequest 함수를 구현합니다.

@Post('/post')
public postRequest(@Body() body: PostBodyType): ResponseType {
  return { type: 'POST', data: body.data }
}

테스트

생성한 GET, POST 요청을 테스트해보겠습니다.

yarn start

GET

curl -XGET 'localhost:3000/get'
{"type":"GET","data":"getResponse"}

POST

curl -XPOST -H "Content-type: application/json" -d '{ "data": "postResponse" }' 'localhost:3000/post'
{"type":"POST","data":"postResponse"}

바이너리 생성

무스마 개발팀에서는 Node.js 프로젝트를 도커 이미지로 생성하여 배포하고있습니다.

Node.js 프로젝트를 도커 이미지로 생성할 때는 크게 두 가지가 있는데요.

  1. node 이미지를 사용하는 방법
  2. 실행 파일을 만들어 리눅스 이미지에서 실행하는 방법

두 가지 방법 모두 장단점이 있지만 1번의 방법보다 2번의 방법이 배포할 때 쉽고 간편하므로 2번을 이용하고 있습니다.

이번에는 pkg를 이용하여 Node.js 프로젝트를 하나의 바이너리로 생성하는 방법을 알아보겠습니다.

pkg 설치

pkgdevDependencies에 설치합니다.

yarn add pkg --dev

엔트리 포인트 등록

pkg를 이용하여 하나의 바이너리를 만들기 위해서는 엔트리 포인트 지정이 필요한데요.

이는 pakcage.jsonbin 프로퍼티를 이용하여 지정이 가능합니다.

해당 내용은 pkg에서 확인하실 수 있습니다.

package.json에 아래와 같이 bin 프로퍼티와 엔트리 포인트를 추가합니다.

{
  ...
  "bin": "dist/main.js",
  ...
}

스크립트 등록

pkg 명령어를 이용하기 위한 스크립트를 생성합니다.

현재 macOS-Bigsur 환경에서 실습을 진행하고 있으므로 이에 맞춰서 패키징을 해보겠습니다.

target 옵션에 대한 자세한 내용은 여기에서 확인하실 수 있습니다.

{
  "scripts": { 
    "build:pkg": "pkg . --target node14-macos-x64 --output dist/server",
}

바이너리 생성

등록한 스크립트를 이용하여 바이너리를 생성합니다.

yarn build:pkg
$ pkg . --target node14-macos-x64 --output dist/server
> pkg@5.3.3
> Fetching base Node.js binaries to PKG_CACHE_PATH
  fetched-v14.17.6-macos-x64          [====================] 100%

✨  Done in 17.26s.

결과는 output 옵션에 적힌대로 dist 폴더에 server라는 이름으로 생성됩니다.

ls dist
app.controller.d.ts        app.service.js
app.controller.js          app.service.js.map
app.controller.js.map      main.d.ts
app.module.d.ts            main.js
app.module.js              main.js.map
app.module.js.map          server
app.service.d.ts           tsconfig.build.tsbuildinfo

바이너리 실행 및 테스트

생성된 바이너를 실행해보겠습니다.

dist/server 
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [NestFactory] Starting Nest application...
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [InstanceLoader] AppModule dependencies initialized +14ms
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [RoutesResolver] AppController {/}: +1ms
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [RouterExplorer] Mapped {/get, GET} route +1ms
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [RouterExplorer] Mapped {/post, POST} route +0ms
[Nest] 11971  - 2021-10-07 2:40:37 PM     LOG [NestApplication] Nest application successfully started +1ms

GET 요청을 테스트해보겠습니다.

curl -XGET 'localhost:3000/get'
{"type":"GET","data":"getResponse"}

정상적으로 작동하는 것을 확인할 수 있습니다.

정리

프로젝트를 배포하기 전 까지의 과정을 알아보았습니다.

  • 패키지 설치
  • 프로젝트 생성
  • 기능 구현
  • 바이너리 생성

이 다음 시간에는 도커 이미지 생성 ~ 프로젝트 배포까지 다루어보겠습니다.

무스마 백엔드 살펴보기 - 개발환경 세팅부터 배포까지 (2/2) 편에서 계속됩니다.