👨‍💻 DB Model - Memo (1)

지난 포스팅에서는 저희가 작업하고있는 프로젝트의 네트워크 전송량을 줄여 속도를 높여주는 작업을 Compress라는 모듈을 이용해 해주었습니다.

테스트를 진행했을때는 거의 1/10가량 속도가 빨라진것을 볼 수 있었습니다.

 

오늘은 다시 본래의 기능구현으로 돌아와서 우리가 만들고자하는 서비스에 필요한 Table을 만들어 주도록 하겠습니다.

📌 Model Schema 구상

프로젝트를 Memo라고 지었지만, 일반적인 메모장을 만들자니 무언가 실생활에서 사용하지 않을것 같아서 이번에 실제 배포까지 다룬후 사용할 예정이라 메모+TodoList 기능으로 만들고자 합니다.

 

스토리는 대충 이렇습니다. 

유저는 자신의 할일에대한 제목과 내용을 작성하고 언제 이 일을 해야하는지 날짜도 지정해줍니다. 해당 당일 그 임무를 수행후 active를 true로 설정해주면 그날 할일을 얼마나 진행했는지 화면에 표시해줍니다.

 

뿐만 아니라 유저들은 그룹을 만들고 해당 그룹들이 공통으로 수행해야할 TodoList를 생성하고 공유할 수 있습니다.

 

GROUP MODEL

  • name - 그룹이름 (필수 값)

  • leader - 그룹장 (default = 그룹 생성자)

  • users - 그룹에 소속된 유저들 (default = [그룹 생성자])

  • memos - 그룹에 속한 메모들


TODO MODEL

  • title - 메모장의 제목 (필수 값)

  • memoColor - 메모장의 색상 (default = primary)

    • 메모의 구분을 위해사용

  • doDate - 해당 내용을 이행해야 하는 날짜 (필수 값)

    • type: 날짜형식

  • content - 메모내용 (필수 값)

  • activate - 수행완료여부

    • type: Boolean

    • true: 수행완료

    • false: 미수행

  • user - 생성한 유저

    • 누가 생성했는지를 확인하기 위함

    • 내가 생성한 todo list만 식별하기 위함

  • createdAt - 생성시간 (필수 값)

 

이렇게 유저그룹과 메모장을 생성하기위한 테이블 두개를 생성해 주었습니다.

오늘 포스팅은 여기까지고, 다음 포스팅에서 이 두 모델들을 사용해줄 URI와 그에 필요한 컨트롤러를 만들 대략적인 설계를 진행해 보겠습니다.

Posted by Kim_gorilla

 

👨‍💻 Compression 미들웨어 설치

 

📌 Compression이란

내가 운영중인 웹서비스의  응답속도를 빠르게 하는 방법이 무엇이 있을까요?

웹서버와 WAS를 함께사용해서 웹서버의 설정으로 향상시키는 방법도 있겠지만, 오늘은 Javascript의 compression 미들웨어를 통해 향상시키는 방법에 대하여 같이 학습하고 이번 토이프로젝트에 적용시켜 보겠습니다.

 

compression은 gzip압축을 이용해 응답속도를 향상시켜주는 미들웨어입니다. 

 

(앞으로 설명드릴 내용은 아래의 블로그를 참고및 발췌하여 작성한 내용임을 알려드립니다.)

https://taetaetae.github.io/2018/04/01/apache-gzip/

gzip이란 무엇인가..?

이를 사용하기 위해서는 브라우저에서 gzip을 지원해주어야 하는데 https://caniuse.com/#search=gzip 를들어가 확인해보면 현재 거의 대부분의 브라우저들이 gzip을 지원해 주고있습니다.

 

⚡ gzip의 데이터 흐름

gzip 사용전

  1. 브라우저가 서버측에 /index.html을 요청한다.
  2. 서버는 Request를 해석한다.
  3. Response에 요청한 내용을 담아 보낸다.
  4. Response를 기다렸다가 브라우저에 보여준다. (100kb)

gzip 사용후

  1. 브라우저가 서버측에 /index.html을 요청한다.
  2. 서버는 Request를 해석한다.
  3. Response에 요청한 내용을 담아 보낸다. 여기서 해당 내용을 압축하는 과정이 추가가 된다.
  4. Response header에 압축이 되어있다는 정보를 확인후 브라우저는 해당 내용을 받고(10kb), 압축을 해제한 후 사용자에게 보여준다.

결론적으로 말씀드리자면, gzip을 이용하면 서버가 사용자에게 응답하는 메시지를 압축해서 보내주기 때문에 네트워크 비용(전송되는 데이터양)을 줄일 수 있어 응답이 빨라진다는 이야기입니다.

 

⚡ gzip은 만능인가?(무조건 사용?)

SW개발에 있어서 무조건적인 해답은 없습니다. 가장 상황에 적합한 Best Choice만 있을뿐이지 Best solution은 없다고 생각합니다. 

 

물론 gzip을 이용하면 네트워크 비용이 절약이되어 응답속도가 빨라진다는 장점이 있겠지만, 반대로 클라이언트 입장에서는 받은 데이터를 다시 해독하는 과정이 필요해짐에 따라 CPU를 사용하게되어 오히려 랜더링이 늦어지는 상황이 발생할수 있습니다.

 

결론적으로 상황에따라 사용전과 사용후의 성능을 비교해서 사용하는것이 BEST WAY다 라고 말씀드릴수 있을것 같아요!

📌 Compression 설치 및 적용

( 이제부터 작성되는 내용은 아래의 사이트를 참고하여 진행됨을 말씀드립니다. )

https://www.tutorialspoint.com/koajs/koajs_compression.htm

 

위의 링크를 타고 들어가면 가장 상단설명에 이런 글귀가 보입니다.

번역 :

Compression은 우리의 사이트의 대역폭과 속도를 절약하는데 간단하고 효율적인 방법입니다. 이것은 오직 최신 브라우저와만 호환이 되어지며 만약 우리의 유저가 오래된 브라우저를 사용할때는 이를 주의해서 사용해야합니다.

 

하지만, 위에서 설명했듯이 대부분의 브라우저가 gzip을 지원해주고 있음으로 크게 걱정하실필요는 없을것 같습니다.

( 아마.. 그렇겠죠? )

 

그럼 우리의 프로젝트에 설치를 진행해볼게요. 아래의 사진처럼 yarn add koa-compression을 이용해 설치해 주세요.

예시코드를 살펴보겠습니다. 예제코드는 아래의 링크를 참고하겠습니다.

https://github.com/koajs/compress

(하이라이트 부분을 볼게요.)

위의 소스에서 compress 안에 옵션들에 대해서 좀 알아보도록 할게요.

  • filter
    : response의 constent type을 확인하여 압축여부를 결정하는 선택적 기능입니다. 디폴트로는 압축기능을 사용합니다.
  • threshold
    : 압축시킬 response 메시지의 byte단위 Minimum사이즈입니다. 디폴트로는 1024byte(=1kb)를 사용합니다.
  • flush
    : 압축을위해 사용되는 부분입니다. 해당 소스와같이 작성해주시면 됩니다.

 

위의 사진을 보면 compress를 가져와서 사용하는것을 볼 수 있습니다. 이상하게도 아래에 zlib은 설치를 한적이 없는데 불러오고있는 모습을 볼 수 있는데 역시나 소스코드를 실행하면 없는 라이브러리라는 오류가 나옵니다.

 

고로 zlib가 무엇인지 알아보고 설치를 해주도록 할게요.

 

⚡ zlib란?

zlib 은 gzip 등으로 압축된 파일을 읽고 쓰기 위해 꼭 필요한 라이브러리입니다. MRTG 를 이용한 트래픽모니터링 홈페이지를 구축할 때에 반드시 필요하다고 하네요.

 

yarn add zlib 를 이용해 설치를 해줍니다.

아까전의 예제 소스를 우리 프로젝트에 추가해줄게요.

(src폴더 안에 index.js를 열어주세요.)

위와같이 소스코드를 추가해주었습니다.

이제 테스트를 진행해 볼껀데요, 지금 우리가 기존에 작성한 코드는 문자만 전송해주는 간단한 응답이다보니 이루가 threshold로 지정한 크기보다 작은 데이터가 클라이언트에게 보내집니다.

 

그래서 우리가 이번 프로젝트 포스팅 초반에 만들어주었던 react 폴더(front)의 index.html을 전송해주는 소스코드를 작성해 주도록 하겠습니다.

 

일단 외부 특정파일(html과 같은)을 연결해주기 위해서는 몇가지 모듈을 설치해 주셔야 합니다.

아래의 3가지를 설치해 주셔야 하는데요.

koa-static은 정적파일 즉 사진이나 html파일과 같은 고정된 파일들을 사용하기 위한 모듈입니다.

먼저 설치를 진행할게요.

yarn add koa-static

 

설치후 src/index.js 의 상단에 koa-static 과 path 모듈을 불러오겠습니다.

(path 모듈은 node에서 기본적으로 지원하는 모듈이라 별다른 설치가 필요 없답니다 ^^)

path 와 static을 이용해서 우리는 정적파일들이 모여있는 폴더에 클라이언트가 접근을 허용해주는 소스코드를 작성해 주겠습니다.

(front 작업에서 build 파일을 만들어줄겁니다.)

이렇게 설정해 줌으로서 우리는 클라이언트에게 build폴더안의 파일에 접근을 가능하게 해주었습니다.

 

이제 이 폴더안에 index.js 파일을 클라이언트에게 보내주는 부분을 작성할껀데요. 이렇게 파일을 전송하기 위해서는 koa-send라는 모듈을 설치해 주셔야합니다. 

yarn add koa-send

그리고 src/index.js 상단에 send를 불러와 줄게요.

그다음 이 send를 이용해 클라이언트에게 index.html을 보내주겠습니다.

이렇게 작성한 후 front 폴더에서 build 폴더를 만들어 주도록 하겠습니다.

 

VScode로 이전에 우리가 만든 front 폴더를 열고 콘솔창에 yarn build 를 해주겠습니다.

(자동으로 build 폴더가 만들어지는데 이 이유는 front에서 설명해 주겠습니다.)

이제 테스트를 진행해 보겠습니다.

 

처음으로는 compress를 사용하지 않았을때 시간입니다.

(koa에서는 미들웨어 설정 위치가 중요합니다. 제가 처음에 compress위치를 잘못 넣어줬어요 ㅜㅜ 아래 사진위치로 옴겨주시기 바랍니다.)

이번에는 compress를 사용했을때 시간입니다.

자 속도가 확연하게 줄여진게 보이시나요?

 

오늘은 이렇게 compress를 통해서 네트워크 전송량을 절약하는 방법을 배우고 적용해 보았습니다.

수고하셨어요!

 

다음에는 메모장데이터를 DB에 저장하기위한 작업을 진행해 주겠습니다.

Posted by Kim_gorilla

 

👨‍💻 REST API-Login

지난 포스팅에서 저희는 RegisterLocal 기능을 완성시켜주었습니다. 오늘은 회원가입된 정보를 이용해서 로그인을 처리해주는 Controller method를 만들어보도록 하겠습니다.

📌 Login Controller method

Login 관련 데이터처리는 authentication과 관련되어 있으므로 auth.ctrl.js에서 작업을 해줄겁니다. 일단 해당 파일을 열어주시기 바랍니다.

아래의 사진에 하이라이트 부분을 지금부터 작업해주겠습니다.

일단, 로그인에서도 유저(=클라이언트)로부터 보내진 데이터가 있을겁니다. 로그인이니깐 email, password정도가 되겠네요.

 

이 보내진 email과 password는 우리가 원하는 형식인지 검증해주는 절차를 회원가입때와 마찬가지로 진행해주어야 합니다.

(다시한번 Joi를 사용해야겠네요.)

검증이 실패했을경우 클라이언트에게 오류메시지가 나타나도록 설정을 해주는 소스도 추가해 주겠습니다.

그 다음엔 검증된 정보를 가지고 이정보(email, password)가 데이터베이스에 실제로 존재하는지 검사하는 소스코드도 만들어주도록 할게요.

 

일단 데이터베이스에 관련한 작업임으로 try catch 블록안에서 작업을 진행할게요.

이전에 우리가 User.js에서 만들었던 email을 가지고 DB에서 특정 데이터를 추출하는 findByEmail함수를 사용하겠습니다.

User.js에서 findByEmail 메서드
auth.ctrl.js에서 loginlocal 메서드

위의 그림에서 86번째줄처럼 찾아서 가져온 유저의 정보가 존재하는지 아닌지에 따라 로그인을 처리해주는 일종의 조건문을 만들어주겠습니다.

(여기서 최종적으로 해당 email의 정보를 가진 유저데이터가 있는지 검증합니다.)

이부분을 잘 통과했다면, 우리가 email정보말고 받은 또다른 정보인 password가 해당 유저DB data의 정보와 일치하는지를 검증해주어야 합니다.

(여기서 Password일치여부를 검증합니다.)

 

이를 검증하기 위해서는 User Database에 접근해야하는데요, 그래서 User.js에 User의 password 정보와 일치하는지 검증시켜주는 함수를 새롭게 만들어주도록 하겠습니다.

그리고 이 함수를 auth.ctrl.js에서 사용해서 password검증절차까지 완성해 주도록 하겠습니다.

다시 auth.ctrl.js 파일을 열어주세요.

(email을 가지고 유저정보 검증했던 조건문처럼 password 검증도 비슷하게 소스코드를 구성해줍니다.)

여기까지 해주셨다면 유저가 보낸준정보가 DB에 존재하는지에 대한 검증하는 절차는 완료가 되었습니다.

하지만 여기까지만 한다면 로그인이 지속적으로 유지가 되어지지 않을겁니다.

회원가입에서와 마찬가지로 Web Token을 이용해서 로그인을 유지시켜주어야 하는데요, 그 절차를 지금 만들어주도록 하겠습니다.

이렇게 작성해주면 로그인정보를 담은 데이터(Token)이 담긴 세션이 만들어집니다.

(클라이언트에서는 이를 이용해 로그인을 유지시켜주면 됩니다.)

 

위 사진에서 아래에 catch 블록 위의 내용은 추후 리액트에서 사용하기위해 보내주는 정보임으로 아직 작성을 안해도 무관하나 응답메시지를 따로 지정을 해줘야 함으로 따라서 작성해주셔도 무관합니다.

(아래 사진을 참고해주세요.)

여기서 끝나는것이 아니라 우리가 만든 loginLocal method를 우리의 특정 URI에 연결을 해줘야합니다.

이를 위해서 auth/inde.js 파일로 이동을 하겠습니다.

위에 하이라이트한 부분이 우리가 미리 만들어두었던 Login을 위한 URI입니다.

뒤에 있는 Arrow function이 방금 우리가 만들었던 loginLocal 함수가 들어갈 자리입니다.

 

바로 위에 있는 회원가입 URI코드처럼 수정을 해주도록 하겠습니다.

이렇게 해주면 해당경로로 로그인을 시도하는것이 가능해집니다.

한번 서버를 켜고 PostMan을 이용해 테스트를 진행해 보도록 하겠습니다.

(테스트시 사용하는 데이터는 아래와같이 회원가입때 사용한 정보를 활용하도록 하겠습니다.)

아래의 사진처럼 PostMan 하단에서 그결과로 Cookie 안에 accsee_token이 생성된것을 확인이 가능합니다.

이 정보가 올바르게 만들어졌는지 https://jwt.io/ 사이트로 이동해서 검사를 해보도록 하겠습니다.

 

오늘 포스팅은 여기까지입니다. 저희는 REST API기반 회원가입과 로그인 기능을 구현해주었습니다.

 

조금 기나긴 내용이었는데요, 다음 포스팅에서는 한템포 쉬어가는 시간으로 저희가 만드는 서비스의 속도나 효율적인 측면을 상승시켜줄 미들웨어 몇개를 설정해 주도록 하겠습니다.

 

다음 포스팅에서 뵐게요~!😉

Posted by Kim_gorilla

 

👨‍💻 Session&Token (2)

지난 포스팅에서 저희는 Json Web Token 이라는 모듈을 이용해서 토큰을 만들고 해석하는 함수를 만들었습니다. 오늘은 이 함수들을 회원가입 절차에서 사용을 해보도록 하겠습니다.

📌 Register fuction & Web Token

우리가 여기서 원하는것은 회원가입이 이루어졌을때 해당유저에게 token을 만들어주는 것인데요. 우리가 사전에 만들었던 token을 만들어주는 generateToken 함수를 사용해 auth.ctrl.js에서 구현을 해줄수도 있지만, 유저에대한 token은 다른 함수에서도 자주 사용이될 여지가 있기 때문에 따로 User.js 모델파일에서 methods함수를 이용해 만들어주도록 하겠습니다.

 

그럼 db폴더안에 User.js 파일을 열어주세요.

그리고 상단에 제일먼저 우리가 사용할 lib폴더안에 token파일과 토큰을 생성할때(토큰을 해쉬화할때) 사용할 문자열을 .env에서 가져오겠습니다.

그리고 맨 하단에 특정 유저정보(payload)를 담은 토큰을 만들어주는 함수를 만들어줄겁니다.

 

우리가 예전 포스팅에서 DB안에 정보를 처리할때(특정 이메일의 유저 찾기등..) statics를 사용했었는데요.

이때 statics는 DB자체에대한 처리를 할때 사용한다면 method는 특정 유저데이터에대한 처리를 할때 사용합니다.

 

그래서 이번에는 method를 이용해서 아래와같이 소스코드를 추가해줄게요.

(우리가 이전에 만들었던 token.js의 generateToken함수를 이용해서 'user'라는 subject를 가진 토큰을 만들어줍니다.)

이제 우리는 auth.ctrl.js에서 특정 User객체의 generateToken함수를 이용해서 쉽게 토큰을 만들수 있습니다.

 

자 그럼 auth.ctrl.js을 수정해보겠습니다. 파일을 열어줍니다.

그리고 우리가 이전에 만들었던 localRegister 함수를 다시 확인할게요.

 

우리는 아래의 사진처럼 클라이언트로부터 데이터를 받아 우리가 원하는 형식대로 잘 왔는지 Joi라는 모듈로 확인했습니다. 만약 잘못들어왔다면 Error를 발생시켰습니다.

만약 잘 들어왔다면, 클라이언트가 보내준 데이터를 DB에 저장하기위해서 Try Catch구문을 사용해 그 안에서 작업을 해주었습니다.

여기까지하면 우리가 원하던대로 새로운 유저의정보가 DB에 저장되는 소스코드가 완성되었습니다.

 

약간 아쉬웠던점이 회원가입과 동시에 로그인이 안되어지는 것이었는데요, 오늘 그부분을 방금 User.js에서 만든 generateToken을 이용해 처리를 해주겠습니다.

 

위의 사진에서 47번째줄 아래에 토큰을 생성해주는 함수를 넣어주겠습니다. 아래의

사진을 따라 작업해주세요.

(하이라이트한 소스코드를 참고해주세요.)

이전에 DB에 저장을 하면서 만들어졌던 user객체에 우리가 새로만든 generateToken을 이용합니다. 이때 우리가 따로 어떠한 파라미터값을 넘겨주지 않아도 user객체안에있는 display, email 등의 정보를 이용해서 그 정보를 payload로 이용해 token을 알아서 생성이 됩니다.

 

그리고 이정보를 쿠키에 담아서 다시 요청했던 클라이언트에게 보내줍니다.

이때 쿠키의 활성시간은 7일로 설정해주는데 여기에 입력하는 단위는 ms단위 계산임으로 아래 사진과같이 입력해줍니다.

이렇게까지 해주셨다면 회원가입과 동시에 세션이 생성되고 그 안에 토큰이 만들어집니다.

 

서버를 실행시켜 확인해 보겠습니다.

postman을 이용해 아래의 데이터로 테스트를 진행해 보겠습니다.

테스트가 정상적으로 이루어졌다면, postman 하단에 cookie안에 우리가 생성한 token이 존재하는것을 볼 수 있습니다.

그리고 이 token안의 정보를 복사하여서 https://jwt.io/ 사이트로 들어가 제대로 작성이 되었는지 확인해 보겠습니다.

 

이렇게 좌측에 복사한것을 붙여넣기 해주면 우측에 HEADER, PAYLOAD, SIGNATURE 영역별 정보가 나오게 됩니다.

 

즉 PAYLOAD보면 우리가 token을 만들때 넣었던 정보들을 확인이 가능해집니다.

오늘은 회원가입이 이루어졌을때 로그인이 유지되기위해 token을 유저에게 발급해주는 기능까지 구현해 주었습니다.

 

다음은 auth.ctrl.js 안에 loginLocal 기능을 구현해 주도록 하겠습니다.

Posted by Kim_gorilla

👨‍💻 Session&Token (1)

지난 포스팅에 저희는 실제로 DB에 데이터들이 저장되는것까지 구현을 완료했습니다.

이제 회원가입시 회원정보가 유지되도록 하는 기능을 구현해 보도록 할껀데요, 이를 위한 토큰을 생성하는 작업을 먼저 이번 포스팅에서 다뤄보도록 하겠습니다. 

📌 Web token

보안 및 인증을 위해 사용하는 일종의 열쇠같은 문자열덩어리 입니다.

주로 의미가 없는 문자열(랜덤한 문자열) 기반으로 구성되어있고 특정한 정보를 암호화해서 문자열화시켜 사용자에게 발급하는 방식입니다.

 

그리고 사용자는 인증을 위해서 서버에 해당 토큰을 들고와 반납하고 이것을 우리가 읽어서 인증된 유저인지 식별하는것입니다.

 

우리는 웹토큰중에서도 클래임(Claim)기반 토큰을 사용하겠습니다.

Claim한 토큰이란 일반적인 토큰에는 단순한 문자열덩어리기 때문에 특정한 정보를 담기가 어렵다는 단점이 있습니다.

 

하지만, 우리가 사용할 Claim기반 토큰은 정보를 담을 수 있는데 이때 Claim은 사용자  정보나 데이터 속성등을 의미합니다. 

 

직접 구현해서 사용하기에는 저의 내공이 부족함으로 이미 잘 만들어져있는 모듈을 가져다가 사용하도록 할텐데요, 우리가 사용할 모듈은 바로 Json Web Token 입니다.

 

그럼 우리의 프로젝트에 install 해보도록할게요.

( yarn add jsonwebtoken )

/* Json Web Token에 대한 구체적인 설명은 존경하는 velopert님의 글을 발췌해왔습니다. */

(https://velopert.com/2389)

 

Json Web Token은 총 3가지의 정보를 가지고 있습니다.

  1. Header (헤더)

  2. Payload (내용)

  3. Signature (서명)

Header는 두가지의 정보를 가지고 있습니다.

  • type : 토큰의 타입을 지정합니다.

  • alg : 해싱 알고리즘을 지정합니다.

Payload는 토큰에 담을 내용들이 포함되어 있습니다. 여기에 담는 정보의 한 조각을 클레임(Claim)이라고 부르고, 이는 name/value 한쌍을 가지고 있습니다. 그리고 복수개의 클레임을 토큰에 담는것도 가능합니다.

 

그리고 이 클레임의 종류는 총 3가지가 있으며 각각 아래와같이 구분되어 집니다.

  • (등록된) registered claim
  • (공개)    public claim
  • (비공개) private claim

이중에 registered claim은 서비스에 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다.

 

이 클레임에 포함된 클레임들의 이름들은 아래와 같이 있습니다.

  • iss
    : 토큰 발급자
  • sub
    : 토큰 제목
  • aud
    : 토큰 대상자
  • exp
    : 토큰의 만료시간 (현재시간 이후로 설정되어야 합니다.)
  • nbf
    : Not Before를 의미하며 토큰의 활성날자와 비슷한 역할을하며 해당 날짜가 지나가기 전까지는 토큰이 처리되어지지 않습니다.
  • iat
    : 토큰이 발급된 시간
  • jti
    : JWT토큰의 고유 식별자 (주로 중복적인 처리를 방지하기 위함)

Public claim은 충돌이 방지된 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI형식으로 짓습니다.

{
    "https://localhost.com/4000/jwt_claims/is_admin": true
}

Private claim은 등록된 클래임도 공개 클래임도 아니고 양측(서버와 클라이언트)간에 협의하에 사용하는 클레임 이름입니다.

 

Example ( velopert 블로그 예시내용 발췌 )

{
    "iss": "gorillaKim.com",
    "exp": "1485270000000",
    "https://gorillaKim.com/jwt_claims/is_admin": true,
    "userId": "11028373727107",
    "username": "gorillaKim"
}

Signature는 헤더의 인코딩값과, 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬하여 생성합니다.

 

위의 내용을 토대로 우리가 사용할 토큰 생성함수와 그 토큰을 해독시켜주는 함수를 만들어 보도록 하겠습니다.

 

token.js 파일을 만들껀데 이렇게 보조로 도와주는 파일들을 src 폴더안에 lib라는 폴더를 만들어서 따로 관리하도록 하겠습니다.

 

다음의 그림처럼 폴더와 파일을 구성해주세요.

 

그리고 token.js에 두개의 함수를 마저 작성해보도록 할게요.

 

처음으로 토큰 생성함수입니다.

  • payload는 외부에서 생성하여 받아 jsonwebtoken으로 넘겨줍니다. 
  • 그리고 secret은 우리가 만들 토큰을 해쉬화 하기위해 사용하는 salt라 불리는 일종의 문자열입니다.

여기서 secret값은 보안상의 이유로 소스코드상에서 감춰주는것이 좋음으로 .env 파일에 만들어서 불러와 사용해줍니다.

그리고 토큰을 생성했으니 이 토큰을 해독해주는 decode함수도 만들어주겠습니다.

그리고 마지막으로 두 함수를 외부로 보내주는 export작업도 해주겠습니다.

 

여기까지 오늘은 토큰을 읽고해석하는 함수를 만들어 보았습니다. 다음 포스팅에서는 이 토큰함수들을 활용해서 회원가입 절차 성공시 토큰을 발급하는 부분을 완성시켜주도록 하겠습니다.

Posted by Kim_gorilla

 

👨‍💻 REST API (3)

 

📌 LocalRegister Controller(3)

 

지난 포스팅에서는 클라이언트로부터 받은 데이터가 알맞은 형식인지 Validate(확인)하고 그 뒤에 특정 데이터 처리가 가능하도록 셋팅해주었습니다.

 

오늘은 우리가 받은 데이터를 가지고 DB(MongoDB)에 저장을 해주도록 해볼게요!

(드디어.. 모델을 사용해보는군요)

 

데이터베이스와 관련된 작업을 할때는 그에대한 오류처리를 해주는것이 중요합니다. 그렇기 때문에 저희는 try catch 블록 안에서 작업을 해주도록 할꺼에요.

 

우리가 try 블록에서 제일먼저 해주고 싶은것은 유저가 보낸 displayName과 email이 이미 존재하는 정보인지 확인하는것입니다.

 

이러한 DB에 직접 접근해서 하는 작업들은 auth.ctrl.js에서도 작업이 가능하지만, 가능한 model 파일 즉 User.js에서 처리해주는것이 좋습니다.

 

그런이유로 User.js를 열어 3가지의 함수를 만들어 내보내줄꺼에요.

나중에 ID/PW를 찾기위한 두개의 메서드와 DisplayName 과 Email을 동시에 검증을 해주는 메서드 하나를 만들겠습니다.

(Model에서는 statics라는 메서드를 통해 사용자 지정함수를 만들 수 있습니다.)

만들었으니 사용해봐야겠죠? findExistancy 함수를 우리가 만든 auth.ctrl.js의 try 블록에서 사용해 볼게요.

이렇게 해주면 중복검사가 잘 작동하게 됩니다.

 

중복검사까지 했으니 실질적으로 DB에 저장을 해주어야겠죠?

이번에는 DB에 저장을 해주는 메서드를 User.js에 작성해 주겠습니다.

만들었으니 이 함수도 auth.ctrl.js에서 사용해 보도록 하겠습니다.

이렇게 해주면 DB에 회원등록과 동시에 이에 대한 정보를 응답으로 보내주게 되어집니다. 한번 잘 작동하는지 포스트맨으로 확인을 해볼게요.

 

아래 사진 데이터로 전송을하니 사진 하단의 결과물이 반환된것을 확인이 가능합니다.

이번에는 DB에도 잘 작성되었는지 확인해 볼껀데요, 이를 확인하기위해 MongoDB 설치시 같이 받은 Compass라는 도구를 사용해 보겠습니다.

 

Compass를 열면 다음과 같은 화면이 나오는데요, 저는 여러 플젝을통해 여러DB가 생성되었지만 처음 사용하시는 분은 memo만 있을꺼에요.

이 memo를 클릭해주면, 다음처럼 users 테이블이 보일겁니다.

users테이블을 클릭하면 우리가 방금 생성한 User 정보가 있습니다.

아주 잘 생성된걸 보실 수 있습니다. 조금 아쉬운점은 비밀번호가 암호화 되어서 저장이 이루어지지 않았다는 것인데요, 이는 나중에 시간적 여유가 될때 처리해 주도록 하겠습니다.

 

일단 이번포스팅은 여기까지 입니다. 다음 포스팅에서는 회원가입이 이루어졌으니 자동으로 세션을 만들어 로그인이 이루어지고 그 로그인이 유지될 수 있도록 처리해 주도록 하겠습니다.

 

그럼 다음 포스팅에서 뵈요 ^^

 

Posted by Kim_gorilla

 

👨‍💻 REST API (2)

저번시간에 우리는 사전에 만들어두었던 몽고DB의 모델들을 사용하기 위해서 REST API의 Controller를 만들었습니다.

(정확히는 틀만 만들었죠)

 

오늘은 그때 만든 두개의 함수들에 내용을 채워넣어 보도록 하겠습니다.

📌 LocalRegister Controller

회원가입절차이니 사용자로부터 요청된 혹은 보내진 데이터를 받아서 사용해야합니다. 그렇다면 사용자로부터 전송된 데이터를 어떻게 확인할 수 있을까요?

 

아마 express 사용하셨던 분들이라면, 파라미터로 req, res 를 사용하여서 req를 통해 꺼내올 수 있었을겁니다. 하지만 Koa에서는 ctx 하나만 파라미터로 받고있어요.

 

사실 ctx 객체안에 req, res 둘다 들어있습니다. 정확히는 request, response라는 이름으로 들어있습니다.

 

const { body } = ctx.request; 이렇게 작성하게 되면 request 정보에서 body안의 내용을 추출해 사용이 가능해집니다.

 

한번 이 body의 정보를 출력해보고싶은데... 사실 지금 당장은 확인이 불가능합니다. 

이유즉슨, 우리가 서버로 어떠한 데이터도 post 방식으로 보낼 방법이 없고, 또 아직 body를 읽어줄 모듈을 설치해 주지 않았습니다.

 

모듈은 나중에 우리가 설치해 Back단에서 처리가 가능하지만, post방식으로 데이터를 보내는 작업은 Client단 즉 Front에서 해줘야 하는 작업입니다.

 

그래도 걱정하지 마세요, 세상엔 정말 편한 도구들이 많이 있으니까요!

PostMan이라는 프로그램을 받아 데이터를 보내고 그 결과를 확인해보는 테스트를 해볼 수 있습니다.

 

PostMan 설치에 대한것은 이번에 포스팅하진 않겠습니다.
(시간이 된다면 따로 포스팅해드릴게요 ^^)

 

포스트맨을 이용하면 POST방식으로 http://localhost:4000/api/v1.0/auth/register/local 경로로 접속했을때 다음과 같은 결과를 확인할 수 있습니다.

뿐만 아니라 아래와 같이 수정후 postman으로 재 요청해보면 ctx.request 객체의 정보도 확인이 가능합니다.

(아직 Body 정보는 확인이 불가능해요)

auth.ctrl.js
auth/index.js

 

하지만 아래에 보는것처럼 undefined 라고 뜨는것을 볼 수 있어요

우리가 아직 아무 데이터도 보내주지 않아서 이렇게 나오는건데, 한번 데이터도 담아서 보내주도록 해볼껀데요 그전에 GET과 POST 방식의 차이를 알아보도록 하겠습니다.

 

GET은 데이터를 전송할때 헤더라는 영역에 담아서 데이터를 보내주고 POST는 바디라는 영역에 데이터를 담아서 보내줍니다. 여기서 바디는 일반적으로 쉽게 읽어올수 없는데요, 이를 읽어오기 위해서는 parser라는 모듈이 필요합니다. 

 

그래서 koa-bodyparser 를 설치해 줄게요 yarn add koa-bodyparser 해주세요.

그리고 미들웨어를 우리의 앱에 추가해주겠습니다. 

(src/index.js로 이동해서 진행하겠습니다.)

이렇게 해주게 되면 우리가 post로 보낸 데이터를 우리 앱에서 읽어올수가 있습니다.

 

한번 테스트를 진행해 보겠습니다.

다시 auth.ctrl.js 로 이동해서 아래와 같이 수정해주세요.

그리고 다시 postman으로 돌아와 아래와 같이 설정하여 데이터를 보내보도록 하겠습니다.

그렇다면 아래와 같이 우리가 보낸 데이터를 받아서 확인이 가능합니다.

이런식으로 클라이언트로부터 받아와 저장을 할껍니다. 

 

자 그럼 클라이언트가 보낸 데이터가 우리가 원하는 형식에 맞게 와야 정확한 데이터 처리가 이루어질껍니다. 그래서 joi라는 모듈을 이용해 일종의 형식 검증절차를 진행해 보도록 할게요!

(https://www.npmjs.com/package/joi)

 

이를 사용하기위해 joi도 설치해 주겠습니다. yarn add joi

그리고 이를 이용해서 우리가 보내줄 데이터를 검증해 볼게요.

우리가 보내줄 데이터는 회원가입임으로, 총 3가지입니다. (displayName, email, password)

 

이들을 검증해주는 코드를 작성해보면 아래와 같아요.

const Joi = require('joi');

// local register function
exports.localRegister = async (ctx) => {
  const { body } = ctx.request;

  const schema = Joi.object({
    displayName: Joi.string().regex(/^[a-zA-Z0-9ㄱ-힣]{3,12}$/).required(),
    email: Joi.string().email().required(),
    password: Joi.string().min(6).max(30),
  });
};

// local login function
exports.localLogin = async (ctx) => {

};

Displayname은 한글 영어 둘다 가능하고 최소3자에서 12자까지 가능하게 했고, 이메일은 이메일형식만 그리고 패스워드는 6~30자 사이만 가능하게 했습니다.

(pw를 암호화해서 저장해야하지만 이는 나중에 작업해주도록 할게요)

 

이제 우리가 받아온 body 정보가 schema 기준에 맞는지 검증을 진행해 보도록 하겠습니다.

다음의 소스코드를  우리가 만든 schema 아래에 작성해주세요.

const result = Joi.validate(body, schema);
console.log(result);

그리고 서버를 실행시킨후 아래와같이 형식에 맞지않게 서버에 데이터를 보내보도록 하겠습니다.

이렇게 형식에 맞지않는 데이터를 보내주면 아래와같이 ERROR가 출력이되어집니다.

이제 간단하게 if문을 이용해서 result에 error를 통해 에러여부를 확인해 주면 될거같습니다. 

다음의 소스코드를 따라 입력해볼게요.

// local register function
exports.localRegister = async (ctx) => {
  const { body } = ctx.request;

  const schema = Joi.object({
    displayName: Joi.string().regex(/^[a-zA-Z0-9ㄱ-힣]{3,12}$/).required(),
    email: Joi.string().email().required(),
    password: Joi.string().min(6).max(30),
  });
  const result = Joi.validate(body, schema);

  // Schema error
  if (result.error) {
    ctx.status = 400;
    ctx.body = 'Schema error';
    // eslint-disable-next-line no-useless-return
    return;
  }
};

그리고 다시 테스트를 진행해 보겠습니다!

결과로 아래의 사진의 하단처럼 Schema error 라는 우리가 지정한 메시지가 나오는것을 볼 수 있습니다.

형식에 맞춰 보내주게 된다면 body 안에는 displayName, email, password 정보가 들어있을겁니다.

그렇다면 const { displayName, email, password } = body를 통해 추출이 가능해질껍니다.

한번 아래의 소스로 수정해서 확인을 해보죠.

// local register function
exports.localRegister = async (ctx) => {
  const { body } = ctx.request;

  const schema = Joi.object({
    displayName: Joi.string().regex(/^[a-zA-Z0-9ㄱ-힣]{3,12}$/).required(),
    email: Joi.string().email().required(),
    password: Joi.string().min(6).max(30),
  });
  const result = Joi.validate(body, schema);

  // Schema error
  if (result.error) {
    ctx.status = 400;
    ctx.body = 'Schema error';
    // eslint-disable-next-line no-useless-return
    return;
  }
  const { displayName, email, password } = body;
  console.log(displayName, email, password);
};

그리고 포스트맨에 가서 아래의 내용을 담아 보낸후 그 결과를 확인해 보겠습니다.

아래의 사진처럼 우리가 입력한 정보가 우리의 서버에 잘 도착한것을 확인이 가능하다면 여기까지 무사히 잘 따라오신겁니다.

이제 Front에서 데이터를 형식에 맞게 보내주면 특정 처리가 가능해졌습니다.

 

이제 이 받은 데이터를 가지고 DB에 저장하는 소스를 작성할껀데요. 어떻게 포스팅을 작성하다보니 너무 길어진 관계로 다음 포스팅에서 마저 진행을 하도록 하겠습니다. : )

 

 

 

 

 

Posted by Kim_gorilla

 

👨‍💻 DB연결 (4)

 

📌 Controller 작성하기

앞선 포스팅에서 저희가 데이터베이스안에 저장을위한 데이터의 틀인 모델을 작성해주었습니다.

이번에는 유저로부터 데이터를 받아서 실제 데이터베이스에 데이터가 저장이 되어지도록 한번 Controller를 작성해 볼게요.

 

Controller는 예전에 우리가 생성했던 api 폴더안에 v1.0 폴더에서 작업을 해줄겁니다.

일단 첫번째로 만들 Controller는 회원가입을 위한 컨트롤러를 만들꺼에요.

v1.0폴더안에 auth라는 폴더를 만들어줄게요. 앞으로 인증과 관련된 작업들은 이곳에서 처리를 하게 될겁니다.

(회원가입도 인증이 필요한 절차입니다.)

 

이전에 했던 작업과 마찬가지로 이곳에서 여러개의 URL경로가 나올예정이니 이 여러개의 경로를 처리할 index.js 와 auth의 controller가 될 auth.ctrl.js 을 만들어 주도록 하겠습니다.

그럼 제일먼저 방금생성한 index.js 부터 수정을 해보도록 하겠습니다.

const Router = require('koa-router'); 

const auth = new Router();

/* /api/v1.0/auth */
auth.get('/', (ctx) => {
  ctx.body = '✅ Welcome to auth!!';
});

module.exports = auth;

위와같이 수정을 했다면, 이 index.js를 api안에 v1.0폴더안의 index.js와 연결해 주어야겠죠?

그럼 마저 auth의 index.js와 v1.0의 index.js를 연결해 주도록 하겠습니다.

const Router = require('koa-router');
const auth = require('./auth');

const api = new Router();

/* /api/v1.0/... */
api.use('/auth', auth.routes());

module.exports = api;

이렇게까지 완료했다면 서버를 켜고 http://localhost:4000/api/v1.0/auth 에 접속해보시기 바랍니다.

위와같이 나왔다면 auth와 연동도 성공입니다! 

 

그리고 이 auth 안에서 회원가입(register)를 위한 Url 경로와 로그인(Login)을 위한 Url 경로를 만들어주도록 하겠습니다.

( auth/index.js에서 작업합니다. )

 

여기서 회원가입과 로그인과정은 보안상으로 노출이되면 안되는 정보들(pw등..)이 포함됨으로 POST방식으로 전송을 해줄겁니다. 

( 아래의 사진처럼 수정을 해주세요. )

const Router = require('koa-router'); 

const auth = new Router();

/* /api/v1.0/auth */
auth.get('/', (ctx) => {
  ctx.body = '✅ Welcome to auth!!';
});
auth.post('/register/local', (ctx) => {
  ctx.body = '✅ Welcome to register!!';
});
auth.post('/login/local', (ctx) => {
  ctx.body = '✅ Welcome to login!!';
});

module.exports = auth;

그리고 각각의 경로로 접속을 해보도록 하겠습니다.

아마 우리가 지정했던 ✅ Welcome to login!! 이 문구가 나오지 않는것을 보실 수 있을겁니다. 이 이유는 우리가 post방식으로 전송을 했기때문입니다. 일단은 이렇게 그대로 두시면 됩니다.

우리가 작성한 소스를 보면 auth.post() 안에서 파라미터로 두개를 받는데 하나는 경로(URL경로) 이고 다른 하나는 해당 URL로의 요청에 대한 처리를 담당하는 함수가 들어가 있습니다. 
( Arrow function = " ()=>{} " 로 되어있습니다. )

 

즉 사용자가 보낸 회원가입정보를 가지고 특정 데이터 처리라든가 DB에 저장한다든가 하는곳이라는 말입니다.

 

우리는 이부분을 auth.ctrl.js에 하나로 모아서 생성하고 관리할꺼에요. 자 auth.ctrl.js를 열어주세요.

그리고 다음의 소스를 작성해줍니다.

우리가 만들고자 했던 local로그인 부분과 local회원가입 관련 데이터 처리를 담당하는 컨트롤러를 만들어주었습니다.

 

그리고 이안에서 우리가 원하는대로 데이터 핸들링을 해줄꺼에요.

 

오늘은 여기까지만 하고 다음 포스팅에서 방금생성한 두 함수의 내용을 마저 채워보도록 하겠습니다.

 

 

 

 

 

Posted by Kim_gorilla

 

👨‍💻 DB연결 (3)

 

📌 model 만들기

이번에는 데이터베이스 안에 생성될 데이터들의 틀을 만들어줄겁니다.

이 틀을 model 이라고 불러요. 음.. 뭐랄까 사용해본 사람의 입장으로서 말씀드리자면 장고의 ORM과 비슷합니다. 아니 몽고디비도 ORM을 지원해준다고 하네요 ^^

 

아무튼! 한번 만들어 보도록 하죠!

 

우리가 어떤 데이터를 집어넣을지를 생각하기 위해서는 일단 어떤 서비스를 만들것인가부터 시작해야합니다.

(사실 이러한 IT 서비스를 개발하는것은 설계단계가 정말 중요해요. 먼저 선수되어야 하는 과정이지만, 사이드 프로젝트이기 때문에 과감히 생략합니다. 이후 생기는 문제는 미래의 나에게 맡기.. )

 

일단 우리가 만들 서비스는 간단한 TodoList 메모장을 만들겁니다.

근데 너무 간단하면 재미가 없으니깐, 회원가입을 통해 유저별 메모장을 관리해주고 그룹생성을 통해 그룹별 공통 할일을 지정할 수 있도록 할게요.

 

자 그럼 어떤 테이블들이 필요할까요?

  1. 유저 테이블 (회원관리)
  2. Todolist 테이블 (메모들)
  3. 그룹 테이블 (그룹정보)

정리해 보니 이정도가 될꺼같네요. 추후 필요해지는 정보들을 그때가서 update하도록 하겠습니다.

 

그럼 유저테이블 생성을 위한 모델을 만들어볼까요? 이름은 User 로 할게요.

모델을 생성하기전에 이 모델들을 관리할 models라는 이름의 폴더를 db폴더안에 생성해주고 그안에 User.js를 만들어주도록 할게요.

이안에 모델을 위한 소스코드를 작성해보도록 할게요.

 

들어갈 내용은 닉네임, 이메일, 패스워드, 그리고 계정생성시간 정도?

다음의 사진처럼 작성해주세요.

const mongoose = require('mongoose');

const User = new mongoose.Schema({
  displayName: String,
  email: String,
  password: String,
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('User', User);

유저 모델을 만들었으니 다음은 Todo 모델을 만들어보도록 하겠습니다.

(이름은 Todo.js 라고 할게요.)

 

안에들어갈 내용은 Todo 제목, 내용, 완료여부, 생성한사람, 생성시간 정도?

다음 사진처럼 만들어보도록 할게요.

const mongoose = require('mongoose');

const Todo = new mongoose.Schema({
  title: String,
  content: String,
  complete: Boolean,
  user: { // 해당 메모를 작성한 User
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Todo', Todo);

이렇게 메모를 만들기위한 기초모델은 다 만들어졌고, 그룹을 만들고 그룹별 할일 추가 기능은 유저 기능과 유저별 메모기능을 다 만든 다음에 추가하도록 하겠습니다.

 

여기까지 DB관련내용이었구 다음 포스팅 부터는 특정 경로로 요청시 회원가입이 이루어지도록 Controller 작성작업등을 진행하도록 하겠습니다.

 

수고하셨어요

 

Posted by Kim_gorilla

 

👨‍💻 DB연결 (2)

 

📌 MongoDB와 서버앱을 연결시켜주기.

 

지난번 포스팅때에는 MongoDB를 설치해주었고 이 DB와 우리가 개발하고 있는 서버와 연결을 도와주는 Mongoose라는 모듈도 설치해 주었습니다.

 

그래서 이번 포스팅에서는 mongoose를 우리 서버파일에서 불러오고 실제로 연결까지 해주는 작업을 진행해주도록 하겠습니다.

 

require('mongoose') 한다음에 index.js에서 직접적으로 연결시켜주는 방법도 있지만, 이렇게 모든 작업들을 한 파일에서 작성하면은 index.js 소스코드가 길어져서 모든 구조를 파악하기 어려워질 뿐더러 관리가 어려워지기 때문에 DB와 관련된 작업은 src 폴더안에 db폴더를 만들어서 그 안에서 작업을 해주도록 하겠습니다.

 

새로 생성한 db 폴더안에는 index.js 파일이 있고 이 파일에서는 DB 연결과 해제와 관련된 함수들을 작성해 외부에서 쉽게 사용이 가능하도록 만들어 주겠습니다.

(이렇게 하는 이유는, DB에 접근을 한 파일에서만 가능한게 아니라 필요에 따라 재 새용할 수 있는 소스코드로 만들어 다른곳에서도 사용이 가능케 하기 위함입니다.)

const mongoose = require('mongoose');

const mongoUri = 'mongodb://localhost:27017/whereCar';

module.exports = (function () {
  mongoose.promise = global.Promise;

  return {
    connect() {
      mongoose.connect(mongoUri, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
      }).then(
        () => {
          console.log('✅  Successfully connected to mongodb!');
        },
      ).catch((e) => {
        console.error(e);
      });
    },
  };
}());

mongoose는 connect라는 메서드를 지원해주며, 파라미터 값으로는 해당 DB의 위치값을(mondoUri) 넣어주고 그 다음에는 여러 옵션들을 넣어주게 됩니다.

 

위의 함수는 일반 방식과는 다르게 connect함수가 비동기적으로 이루어진다는 특성을 이용하여 [.then] 과 [.catch] 라는 기능을 통해 연결이 성공적으로 이루어졌는지 여부를 화면에 띄워주게 합니다.

 

조금 아쉬운점은 mongoUri 와 같이 외부에 알려져서 안되는 부분들 입니다. 그래서 이부분을 .env 파일로 옴겨서 사용하도록 하겠습니다.

이제 src 폴더 안에 index.js에서 우리가 방금 작성한 파일만 호출하여 사용하기만 하면 됩니다.

아래의 사진처럼 말이죠

이제 다시한번 서버를 실행시켜 보도록할게요.

아래의 사진처럼 mongoDB가 연결이 성공했다는 메시지가 뜬다면 성공입니다.

여기까지가 DB연결을하는 작업이었고 무사히 이를 마치었습니다. 그렇다고 DB관련 작업이 모두 끝난것은 아니랍니다.

 

DB관련 설계작업을 해주어야하는데요. 보통은 스키마 설계같은것들을 해주어야 하지만... 

우리가 사용할 mongoDB는 다른 DB와는 다르게 스키마가 존재하지 않습니다. 왜 없는가를 설명하기 보단 이를통해 얻는 이점을 말씀드리자면, 객체나 리스트같은 구조를 한 칼럼에 저장이 가능하다는 것이죠.

 

스키마를 작성하는 대신에 mongoDB는 model이라는 파일을 만들어서 데이터베이스에 추가될 데이터에 대한 형식(틀)을 지정해주어야 합니다.

 

고로 다음 포스팅에서 이러한 작업들을 해주도록 하겠습니다.

Posted by Kim_gorilla