TIL - Node.js에 TypeScript 적용하기(feat. NodeBird) (2)

MinWoo Park·2021년 5월 18일
2

TIL

목록 보기
45/49
post-thumbnail

Today I Learned

매일 배운 것을 정리하며 기록합니다.
ZeroCho, Node.js에 TypeScript 적용하기(feat. NodeBird) 강의를 통해 공부하였습니다.


01. 초기 세팅

  1. npm init - package.json을 만들어서 node 프로젝트를 만들기

  2. npm i typescript

  3. tsconfig.json 생성 - 타입스크립트 프로젝트라고 알려주는 파일, 타입스크립트 설정함.
    compilerOptions에서 타입스크립트는 strict를 true 안하면 타입스크립트 의미가 퇴색 됩니다.
    lib은 최신 문법들 다 쓸 수 있게 최대한 추가 es2015~es2020 합니다. moduleResolution은 classic과 node가 있는데 당연히 node입니다.
    classic은 옛날 모듈들 es2015모듈 같은거이고, node는 commonJS 입니다.

  4. npm i @types/node

  5. index.ts 생성 - ts 파일은 노드가 직접 실행할 수 없어서 자바스크립트로 바꿔서 실행해야 합니다.
    한 번의 트랜스파일 과정이 필요합니다.

  6. npm i express @types/express - 타입스크립트에서는 보통 const-require보다 import as A from ‘A’; 를 사용합니다.
    as’에 대해 설명 드리자면, import하는 파일에서 가져오는 모듈이 export default A 이면 ‘ as’가 안 붙어도 됩니다.
    그렇지 않다면 ‘
    as’를 붙여 줌으로써 export defalut처럼 만들 수 있습니다. (module.exports는 default랑 좀 다릅니다.)
    node_modules - @types - index.d.ts 파일로 들어가서 코드 확인 가능합니다.
    커맨드 + f로 export default 찾아보고 없으면 ‘ as’붙여줘야 합니다.
    혹은 export default가 있고 그걸 가져오고 싶으면 그냥 import A from ‘A’를 씁니다.
    지금 예시로 A 자리에 express를 넣으면 ‘
    as’를 써야하는데 안 쓰고 사용하는 코드가 있다면 tsconfig.json에서 esModuleInterop: ‘true’ 설정을 주어서 그런 것입니다.
    엄밀히 따지면 import A 랑 import * as A랑은 다른 것이기 떄문에 이해하고 편의를 위해서 사용할 순 있지만 모르고 사용하는 건 안 됩니다.
    원리에 대한 이해가 우선입니다.

'* as'의 사용 이유는 기본적으로는 commonjs와 es2015 모듈의 차이점때문입니다.

import A from 'B'는 B의 default를 불러오는 것인데 commonjs에는 default가 없습니다.

module.exports는 default랑 좀 다릅니다.

따라서 module.exports는 *으로 불러와야 합니다.


02. express와 ts-node

노드가 ts를 바로 실행하는 방법(엄밀히 말하면 바로는 아님 ts->js로 바꾸고 바로 node가 실행하는 것임 ts-node 라이브러리가 내부적으로 트랜스파일링 해 주는 것임)

  1. npm i -D ts-node

  2. npm i -D nodemon

  3. npx ts-node index.ts
    npx 사용 이유는 글로벌 설치를 막기 위해서입니다.
    글로벌 설치 단점이 package.json에 기록이 안 되서 프로젝트 인수인계할 때 따로 문서를 관리해야 합니다.
    번거롭기에 글로벌 설치를 피하는 추세입니다.
    그래서 dependencies나 devDependencies에 설치하고 npx명령어로 글로벌 설치 없이 실행할 수 있습니다.

ts-node는 배포용으로 쓰기에는 무리입니다.(성능에 악영향)

ts-node는 자바스크립트 코드로 그 때 그 때 바꿉니다.
배포 시에는 클라이언트 요청 하나 올 때 처리도 버거운데 ts->js 변경 작업까지하면 쓸데없이 작업이 하나 더 늘게 됩니다.

배포 환경에서는 미리 $ npx tsc 로 타입스크립트를 자바스크립트로 컴파일 후 자바스크립트를 배포 합니다.

즉 개발 시는 ts-node로 편리하게(tsc 후 만든 js를 node로 실행하는 과정을 한 번에),
배포는 tsc로 트랜스파일 후 자바스크립트 코드로 배포

참고로 비슷하게 babel-node가 있습니다.
노드에서 지원하지 않는 최신 문법을 바벨을 통해서 트랜스파일을 하면 최신 문법을 사용할 수 있습니다.

바벨 노드도 배포 시 성능상 무리와서 배포 시에는 미리 트랜스파일 후 js코드로 배포합니다.


03. module resolution

개발을 하다보면 실무에서 타입이 잘못 되어 있는 경우가 있을 수 있습니다.
이러한 상황을 마주하면 타입핑이 어떻게 되어있는지 찾아가야 하는 상황이 올 수 있습니다.
이는 tsc에 traceResoluttion 옵션을 주어 찾아갈 수 있습니다.

$ npx tsc —traceResolution : 개발할 때 한 번씩 테스트용으로 합니다.(타입스크립트의 타입을 어떻게 찾는지 궁금할 때)
예를 들어, 타입핑을 기본적으로 제공하지 않는 express같은 거는 커뮤니티가 만든 @types/express를 같이 설치해야 하는데, 타입스크립트가 과연 타입핑을 어떻게 찾아올까?

$ npx tsc —traceResolution 하면 다음과 같이 나옵니다.

$ npx tsc —traceResolution

모듈 찾아가는 순서는 공식문서 Documentaion - Handbook - moduleResolution 참고하면 됩니다.

04. express 미들웨어

미들웨어 패키지를 설치할 때 주의할 점이 있습니다.

대부분의 패키지들이 타입 지원을 안해서 타입패키지를 추가로 설치해야 합니다

이 때 일반 패키지와 타입 패키지의 버전이 일치하는지 확인해야 합니다.

예를 들어, bcrypt가 5버전이면 타입도 5버전이여야 하는데 타입 패키지 버전은 현재 4버전 일 수도 있습니다.

메이저 버전(맨 앞자리)가 같아야 안정적입니다. 살짝 문제가 될 수도 있습니다.

해결책은 2가지 입니다.
1. 일반 패키지 bcrypt를 다운그레이드하여 4버전을 쓰거나(버전 일치 시키기 위함)
2. 에러 발생 시 types/bcrypt를 커스텀마이징해서 쓰거나


05~06. 시퀄라이즈

타입스크립트랑 데이터베이스 쓸 때는 Sequelize 또는 TypeORM 또는 많이 씁니다.

시퀄라이즈는 특유의 문법이 있습니다.
sequelize.query로 sql쿼리문 할 수도 있습니다.(orm으로 하기 어려운 것들 있을 때 사용)

$ npm i sequelize sequelize-cli mysql2 (시퀄라이즈는 내부적으로 타입 지원함(@types 안 받아도 됨))
$ npx sequelize init
config.json -> js로 바꿔서 닷엔브 사용
$ npx sequelize db:create
config.js 설정대로 db 생성 됨.
생성 후에는 config.ts로 변경.

sequelize cli도 js밖에 인식을 못해서 config.js로 환경설정 후 db만들고, ts파일로 변경합니다.

시퀄라이즈를 통해 model 코드를 작성할 때 각 모델과 index.ts의 순환참조로 인해 에러가 발생할 수 있습니다.

순환 참조가 타입핑적으로는 문제가 되진 않지만, 런타임(타입스크립트)에서 에러가 없다고 실제로 에러가 없는 것은 아닙니다.

실제로 실행해보면 에러가 나는 경우가 있음(ex 순환참조, 순환 참조시 두 모듈 중 하나가 빈 객체({})로 처리되서 문제가 발생함, 순환참조 시 타입같은 건 괜찮은데 실제 런타임에 실행되는 코드는 에러가 남)

순환 참조를 피하기 위해서 sequelize.ts를 만들어 연결 설정을 해 주고, 이걸 export해서 유저모델에서 가져다 쓰고 user를 다시 index에서 가져다 씁니다.

마치 임시변수 만들어서 스왑하는 느낌입니다.


07. passport 설정하기

serializeUser, deserializeUser 각각 언제 실행되는지 정확히 알아야 하비다..

serializeUser는 로그인 할 때 한 번 실행,
deserializeUser는 모든 라우터, 모든 요청에 대해서 한 번씩 다 실행 됨. 매번 실행입니다.

serializeUser에서 done의 매개변수로 유저 정보를 메모리에 저장하는건데, 유저 정보 통째로 저장하면 너무 무거우니까 user id만 저장합니다.

그럼 저장한 id를 deserializeUser에서 사용합니다.

자세한 내용은 npm - passport에 있습니다.

npm - passport 중 Sessions


Reference : ZeroCho, 『Node.js에 TypeScript 적용하기(feat. NodeBird)』, Inflearn

profile
물음표를 느낌표로 바꾸는 순간을 사랑하는 개발자입니다.

1개의 댓글

comment-user-thumbnail
2022년 6월 30일

글 잘 봤습니다!
그런데, moduleResolution에서 node가 ES 버전이고, classic이 commonjs 아닌가요??

답글 달기