Monolithic 서버사이드 타입스크립트 세팅 02
지난 글에서 두 가지를 완료 했었다. 물론 잘 작동하는지 확인하기는 어려웠다. tsconfig
, webpack
까지만 잘 설정하고 나면, 확인은 가능할 것 같다. 맨 처음 목차와는 조금 다르긴 한데, tsconfig
와 webpack
을 먼저 설정한 다음 스웨거를 설정하도록 하자.
Express 기본 아키텍처 구성ESLint & Prettier- tsconfig, webpack 설정
- Swagger
- 개발 환경, 운영 환경 분리
- 도커라이징
tsconfig
tsconfig
는 그냥 자바스크립트 이용자는 alias를 VSCode가 인식하게 한다는 면에서, jsconfig
로 대체할 수 있다. 그 방법을 이 글에서 다루지는 않지만, 갓글에서 많은 분들이 제공해주고 있다. 일단 결론적으로 말하자면 현재 tsconfig
의 설정은 아래와 같다.
1 | { |
위는 사용 중인 옵션을 제외하고는 모두 삭제한 것이다. 글을 쓰면서 평소에 아리송 했던 것들도 정리해봤다. 딱 보면 감이 오는 몇 가지를 제외하고, 먼저 baseUrl
과 paths
는 VSCode상에서 부분에서 alias를 설정해주는 것에 의미가 있다. 실제로 컴파일할 때 이 부분을 고려해서 컴파일 해주지는 않는다. (?.. 왜지, 진짜 버그같음) jsconfig
에서도 해당 설정이 존재한다. 마찬가지로 VSCode에서 인식할 수 있게 바꿔주는 역할 정도만 해준다. ts-node
를 사용해서 실행할 때 조차도 인식을 못 해주는데, tsconfig-paths
모듈을 설치하고 -r tsconfig-paths/register
옵션을 넣어줘야 한다. 자세한 내용은 개발 환경 설정 하면서 작성하려고 한다.
emitDecoratorMetadata
와 experimentalDecorators
는 데코레이터 사용을 가능하게 해주는 것과 관련된 설정인데, typedi
와 typeorm
사용시 필요하다. 메타데이터와 관련된 내용은 확인해보지 못 했다. strictPropertyInitialization
이 부분도 typeorm
테이블 모델링을 할 때 문제가 있어서 추가한 부분이다. 클래스 내부의 프로퍼티가 컨스트럭터에서 초기화 되지 않을 때 에러를 뿜는 것인데 이 부분을 꺼줬다.
esModuleInterop
옵션은 commonjs
로 작성된 모듈을 가져올 때, 원래는 import * as Module from "module"
과 같은 형식으로 가져와야 하는데, import Module from "module"
로 가져올 수 있도록, 컴파일 할 때 특수한 중간자를 둔다. namespace object를 만들어주는 함수를 사용하는 것 같다. 자세한 내용은 스택오버플로우에 있다.
모든 내용은 당연히 알지 못하고, (그거 하나하나 공부하면 시작을 못 한다고 생각하기 때문에…) 필요할 때마다 어떤 옵션을 써야 하는지 찾아보는 편인데, 타입스크립트는 tsc --init
명령어를 사용하면 모든 옵션이 있고, 설명도 주석으로 적당히 친절하게 달아둔 편이다. 지우지말고, 필요할 때 찾아가면서 옵션을 설정해주는 편이 좋다.
Webpack
우선, tsconfig
를 통해 VSCode 상에서 alias로 코드를 가져온 것을 인식하게 만들었다. 그렇지만 tsc
명령어로 트랜스파일링을 시도한다고 해도, 이 부분이 실제로 반영되지 않는다. 그래서 webpack
을 사용해서 해당 alias에 맞게 트랜스파일을 할 수 있게 만든다. 또, 번들링을 함으로써 코드를 최적화 하고 도커 이미지 크기를 줄이는 것에도 목적이 있다.
우선 웹팩으로 빌드하는 환경을 만들기 위해서 아래 의존 패키지를 설치한다.
1 | yarn add webpack webpack-cli webpack-node-externals rimraf @babel/{core,preset-env,preset-typescript,plugin-proposal-decorators,plugin-proposal-class-properties} babel-loader -D |
webpack
,webpack-cli
: 웹팩 설정 파일을 읽고 파일들을 번들링 해주는 핵심 역할을 한다.webpack-node-externals
: 필자는node_modules
내용을 함께 번들링 하고 싶어하는 편이다. 여러번 시도를 해봤지만, 성공할 수가 없었다. 특히serverless-webpack
에서 이 부분을 함께 번들링 하지 않는 것을 보고, 그냥 포기 했다.node_modules
를 번들링 대상에서 제외해주는 역할을 하는 패키지이다.rimraf
: OS에 관계없이 삭제하는 명령어를 수행할 수 있다. 빌드된 디렉토리를 삭제해준다.@babel/{core,preset-env,preset-typescript}
: 필자는ts-loader
를 사용하지 않고, 바벨을 통해 해결하는 편이다. 설정이 깔끔해지고, 더 빠르다고 한다. 그 이유와 자세한 내용은 대단하기로 소문난 Toast팀의 블로그를 확인해보자.@babel/plugin-proposal-decorators
: 바벨에서 데코레이터를 트랜스파일링 할 수 있게 해준다.@babel/plugin-proposal-class-properties
: 바벨에서 클래스의 프로퍼티(메서드 말고)를 트랜스파일링 할 수 있게 해준다. 위 두 플러그인은 설정값과 순서도 지켜줘야 한다.babel-loader
:webpack
모듈의rules
에서 로더로 작동하게 될babel-loader
이다.
플러그인들은 코드를 쓰다 보면 추가될 수 있다. 빌드 에러가 나면 해당 부분을 찾아서 필요한 플러그인을 추가해주면 된다.
설치 후 프로젝트 최상단에 webpack.config.js
파일을 만들어주면 된다. 내용은 결과적으로 아래와 같다.
1 | const path = require("path"); |
nodeExternals()
덕분에 과거에 node
옵션으로 추가 했던 많은 부분을 사용 안해도 되는 것 같다 (확실하지는 않다. 간단하게 실험해봤을 때 unresolve
되는 경우는 없었다.). 다만, __dirname
과 filename
은 그냥 사용하면 /
, index.js
가 나온다. 제대로 반영하게 하기 위해서 node
옵션에 __dirname
, __filename
을 false
값으로 둔다. (아마 이것도 추측이지만, webpack은 기본적으로 웹 환경에서 돌아가는 걸 상정해서 __dirname
, __filename
을 덮어 씌우는 것 같다. 그 기능을 꺼주는 걸로 알고 있다.)
@babel/polyfill
은 async await
문법을 컴파일 할 때 문제가 생기지 않게 해준다. 그냥 사용하게 되면 regeneratorRuntime is not defined
라는 에러가 발생한다.
module
부분에 타입스크립트를 어떻게 컴파일할 것인지 나와있다. ts-loader
역할을 대신하게 된다.
resolve
부분에서 @/*
를 src/*
로 바꿔준다. 이제 컴파일을 하게 되면 온전하게 파일 경로를 설정해준다.
컴파일을 위한 webpack
설정은 얼추 마무리 됐지만, Swagger를 설정하면서, 데이터베이스를 붙이면서, 도커라이징을 하면서 조금씩 추가되거나 수정되는 부분이 생기게 된다. 기타 자세한 옵션은 webpack 공식 문서나 간단하게는 이 링크를 확인해봐도 좋을 것 같다.
마지막으로 웹팩 빌드를 위한 스크립트를 붙여주자.
1 | // package.json |
Swagger
스웨거는 API 문서 만들어주는 툴이다. 사용 방법에 대해서 자세히 다루지는 못할 것 같고, 붙이는 것만 확인해보자. 우선 목표는, 주석으로 문서화를 할 수 있게 되어야 하고, 개발 서버에 배포될 때, 웹팩이 해당 주석을 지우지 않아야 한다.
우선 express에서 개발 서버와 함께 띄우기 위해 swagger-ui-express
와 swagger-jsdoc
을 사용한다.
1 | yarn add swagger-ui-express swagger-jsdoc |
swagger-ui-express
: Express 서버에 Swagger 서버가 뜰 수 있게 미들웨어를 제공해준다.swagger-jsdoc
: JSDoc 코멘트 형식으로 API Doc을 구성하게 되면 인식하고swagger.json
을 만들어준다. 이 내용을swagger-ui-express
에 넣어주면 된다.
문제는 코멘트 형식이라 빌드할 때 웹팩을 사용하면 지워진다. 두 가지 방식으로 해당 문제를 해결할 수 있다(있을 것으로 추정된다.). 한 가지 방법은 NODE_ENV
에 따라서 @swagger
가 붙은 코멘트를 지우지 않는 옵션을 넣어주는 것이고, 두 번째는 스타 2개 붙은 webpack 플러그인을 사용하는 것인데, 필자는 그래도 이 템플릿을 프로덕트에도 사용할 예정이라 그냥 첫 번째 방법으로 하기로 했다.
우선 swagger
디렉토리를 src
아래 만들었다. 안에는 index.ts
를 만든다
1 | // src/swagger/index.ts |
빌드를 하면, 하나의 파일인 app.js
에 모든 주석이 들어가게 되기 때문에 ts-node
를 사용해 동작시키는 로컬 환경이 아니라면, app.js
에서 주석을 찾아야 한다. 반면 타입스크립트 파일을 그대로 돌리는 ts-node
를 사용하면, router
아래 라우터들마다 존재하게 된다. 따라서 apiPath
는 컴파일 여부에 따라서 바뀐다. 이제, 일반적으로 사용되는 부분들을 컴포넌트로 만들기 위해서 swagger
폴더에 components.ts
롤 만든다.
1 | import { MESSAGE } from "@/errors/errRequest"; |
에러 메시지를 실제로 만들어내는 Default한 메시지 값과 동일하게 하기 위해서 src/errors/errRequest
아래 메시지를 컨스트로 관리한다. 그리고 errorResult
의 형태는 express에서 마지막에 에러를 모아서 보내주는 부분과 동일하게 맞춰준다. 에러 컴포넌트 말고도, 일반적으로 사용되는 Response를 위와 같이 만들어두고 사용하면 편하다.
아래는 errors
에서 constants를 관리하는 코드이다.
1 | // src/errors/errReqeust.ts |
이제 appLoader
에 /api-docs
로 이어지는 부분을 추가해주자.
1 | // src/app.ts |
src/loaders/index.ts
에서 appLoader
도 await
을 붙여줘야 한다.
이제 그럼 webpack
에서 prod
모드로 빌드 하면 /api-docs
에는 아무 것도 없어야 하고, dev
모드로 빌드하면 어떤 UI가 나와야 한다. 테스트를 위해서 console.log
를 붙이고 확인해봤다.
production 모드
develoment 모드
Swagger의 UI
이제 스웨거가 붙긴 했다. 그렇지만 여전히 주석 형태로 API Doc을 달았을 때 웹팩이 지워버리는 문제가 있다. 이 부분은 웹팩의 optimization
옵션과 관련이 있다. minimizer
를 terser-webpack-plugin
을 이용해서, NODE_ENV에 따라서, 다르게 번들링 하도록 설정했다.
1 | const path = require("path"); |
테스트를 위해 health
를 체크해주는 앤드포인트에 해당 주석을 달았다. 그리고, 실행 환경인 ENV
는 원래는 local
인데, 개발 서버에 올라갔을 경우의 문제를 해결하는 중이니까 ENV
도 dev
로 바꿔준다.
1 | /** |
이제 다시 빌드를 해보고나서 같은 앤드포인트에 가보면 우리가 만든 스웨거가 등장한다.
로컬 환경에서는 어짜피 웹팩이 동작하지 않아서 무조건 살아있게 된다. 따라서 테스트 해볼 필요는 없겠지만, 다음 글에서 로컬 개발환경을 설정하고 나면 확인할 수 있다.
환경변수에 대해서 헷갈릴 수 있을 것이라 생각이 되어 주석을 달자면,
NODE_ENV
는 Node에서 정해진 환경변수이다.NODE_ENV
는development
,production
두 가지 중에 하나이다.ENV
는 필자가 정한 환경 변수이다..env
에 따라서 수정할 수 있다.NODE_ENV
에 대해서 검색해보면 더 많은 정보를 확인해볼 수 있다.
이번 글은 이 정도로 마무리 지으려고 한다. 자세한 Swagger 사용법에 대해서는 다루지 않는다. (필자도 잘 모름… 쓰던 것만 쓰는 편임) 그래도 찾아보면 잘 나오니까 우리는 찾아가면서 쓰도록 하자.
다음 글에서 도커라이징한 다음, 환경 별로 .env
를 설정해두는 방법, 로컬에서 돌리는 방법 등을 설정해두자.
테스트를 해보면서 이 글에 나오지 않은 부분들이 레포지토리에 추가 되긴 했다. 중요한 부분은 아니라서 글에 작성하지 않았다.
지금까지 구성된 내용은 이 링크에서 확인할 수 있다.
후기
개발 서버에서 SwaggerUI를 보내는 방식이 깔끔한 방식인지 모르겠다. 일단 이상한 버그가 있는데, 필자는 함수 안에 주석을 넣고 싶다. 에디터에서 함수를 fold 했을 때, 함께 접히면 좋을 것 같아서. 그런데 어떤 이상한 확률로 그런 식으로 작성된 주석을 웹팩이 지워버렸다. 이유를 모르겠다. 그래서 밖에다 빼고 사용한지 좀 됐는데 그 이후로는 그런 문제가 생기지는 않았다.
1, 2, 3편으로 이어지는 이 글이 사실은 한 번에 설정하는 내용인데 끊어 정리 하느라, 매끄럽지 않은 느낌도 있다. 최대한 독립적인 부분을 독립적으로 수행하게 구성하려 했는데 그런 면에서는 성공적이지 못 했다.
Reference
- https://stackoverflow.com/questions/56238356/understanding-esmoduleinterop-in-tsconfig-file
- https://trustyoo86.github.io/webpack/2018/01/10/webpack-configuration.html
- https://webpack.js.org/guides/
- https://www.npmjs.com/package/swagger-ui-express
- https://github.com/webpack-contrib/terser-webpack-plugin
Monolithic 서버사이드 타입스크립트 세팅 02
https://changhoi.kim/posts/backend/serverside-typescript-setting-02/