~ 2024.5.3
공부를 어느정도 진행하고 생각해본 프론트앤드 개발자(지망생)도 node.js를 공부해야하는 이유 : 리액트도 하나의 package다.
리액트를 사용하려면 npm으로 설치해야한다. 또 원래 js에는 없는 라우터기능도 사용하려면 npm으로 설치해야한다. 즉 리액트 개발환경에 nodejs가 필요함.
그리고 좀 만들어보니까 서버에 어떻게 요청이 전송되는지 모르고, 어떻게 응답이 올 지 모르는데 제대로 기능 구현을 할 수 있을까? 하는 생각이 들었다. 완전하지는 못해도 흐름은 알아야 한다.
또, nodejs로 화면을 구현하는 방법을 배우다가 이게 ssr이구나 하는 느낌이 들었다. 직접적으로 이게 ssr이에요~ 하고 알려주신건 아니지만, 서버단에서 화면을 동적으로 그릴 수 있는 방법을 학습할 때 알게됐다. next도 살짝 맛본적이 있는데 그냥 왜 되는건지 모르고 남들이 쓰라는데로 따라썼었다. 이제 하면 더 잘할 수 있을것 같다.
처음에 자바스크립트는 브라우저에서만 실행할 수 있었다. 브라우저에는 자바스크립트 엔진이 내장되어있어 자바스크립트를 실행 할 수 있는 것이다. 개발자도구에서 코드를 입력하면 동작하는 이유도 엔진이 존재하기 때문이다. ( 이것이 우리가 웹을 조작하기 위한 언어로 자바스크립트를 사용하는 이유이다. HTML을 동적으로 제어하기 위한 언어로 자바스크립트가 개발되었고, 이것을 기업이 브라우저를 만들 때 도입한 것이 시작이다. 표준 규칙을 준수하며 각 브라우저마다 엔진이 존재한다.)
는 어플리케이션에서 데이터를 읽거나 쓰기 위해 사용하는 인터페이스를 말하고, 인터페이스는 접점이라는 뜻이다. 예를들어, 내가 컴퓨터에 값을 입력하려면 키보드를 쓰고 읽으려면 모니터를 봐야한다. 이 때 키보드와 모니터가 하드웨어적 관점의 인터페이스이다. 핸드폰에서 배민으로 배달을 시켜먹으려면 배민을 켜고 화면을 조작해야한다. 이것이 소프트웨어적 인터페이스이고 유저측 인터페이스니까 UI이다.
그럼 어플리케에션 입장에서 데이터를 읽거나 써야한다면? 그 때 사용되는게 API다. 예를들어, 날씨 앱을 만들고 싶으면 기상청에 날씨정보를 요청하고 응답을 받아서 화면에 그릴 수 있다. 이때 날씨정보 데이터를 가져올 수 있또록 기상청에서 제공하는 접점을 api라고 한다. 
이것이 api다. 날씨 정보를 사용하고싶으면 위 주소에 접근하면 된다. 값을 입력하기위해 키보드를 누른것처럼! 더 정확하게 말하면 기상청에서 저것을 제공하고 있기 때문에 내가 기상청에게 저 모양으로 요청을 보내면 기상청은 현재 정보를 응답해준다. 이것이 http api다. 일반적으로 말하는 api는 모두 이런 형태이다.
+) 이해를 위해 영상을 좀 더 찾아봤다. api는 어떤 소프트웨어가 다른 소프트웨어로부터 지정된 형식으로 요청이나 명령 혹은 정보를 받을 수 있는 수단이다. 위에 기상청의 예시에서, 그냥 응답을 받는게 아니라 내가 api를 통해서 기상청에 접근하면, 그 api로 기상청에게 데이터를 주도록 조작 할 수 있는 것이다. 내가 기상청 컴퓨터가 어떻게 구성되어있는지 알 수 없어도 제공된 api를 사용하면 정보를 제공하도록 동작하게 만들 수 있다.
리액트는 정해진 규칙안에서 작성하도록 되어있으니까 프레임워크가 아닌가? 했는데 해당블로그 글을 보면 리액트는 웹 개발을 할 때 필요한 모든 기능과 툴을 포함(프레임워크)하는것이 아니라 ui를 만드는 기능만을 제공하기 때문에 라이브러리라고 한다.


즉, 자바스크립트를 실행하려면 자바스크립트 엔진이 필요하다. 브라우저 밖에서 자바스크립트를 실행하려면 엔진을 외부에서 사용하면 된다는 아이디어로 node.js가 탄생했다. node.js를 사용하면 브라우저 밖에서도 자바스크립트를 이용 할 수 있게 된다.
자바스크립트 소스코드가 실행되면 자바스크립트 엔진인 v8이 동작한다. 소스코드안에 데이터베이스에 접근하거나, 파일을 읽는 등의 js 외의 작업이 있으면 libuv가 처리한다. 브라우저환경이 자바스크립트의 비동기작업을 지원하는 것처럼 libuv도 브라우저 외 환경에서 비동기작업을 지원한다. node.js는 이 둘을 결합한다. 
node --version으로 설치를 확인 할 수 있고, node를 입력하면 node환경으로 진입한다. 탈출은 .exit 혹은 control+cRead-Evaluate-Print Loop는 특정 코드를 입력하면 그 코드를 평가하고 코드의 실행 결과를 출력하는것을 반복하는 환경을 말한다. 우리가 터미널에서 node환경이나 크롬개발자도구에서 코드를 입력했을 때 아래 이미지와 같이 동작하는 것이 RELP이다. v8이 하나씩 해석한다. 
모든 기능을 스스로 만들 수는 없다. (만들어도되지만) node.js에는 기본으로 제공하는 많은 모듈이 있고, 개발자는 그것을 require로 가져와서 사용 할 수 있다.

const module = require('파일경로와 파일이름.파일타입') (.js의 경우 생략 가능. 모듈 키워드의 익스텐션에 .js -> .json -> .node순서로 실행해준다고 되어있음.) require()함수를 사용하여 자바스크립트 파일을 읽고 그 파일을 실행하여 객체를 반환한다. 변수 또는 상수에 할당하여 객체를 사용 할 수 있다.


함수를 만들고,
module.exports ={}
에 포함시켜서 exports한다. (이때 함수를 전달하지만 바로 실행 하는게 아니니까 함수명만 적어줘야 한다.)
사용할 때는 require()를 사용하는데, 객체의 형태로 exports했음으로 비구조화할당을 통해서 변수에 할당 할 수 있다.


위에서 작성한 방법은 CommomJS Module로 node.js에서 사용하는 기본 모듈이다. 브라우저 환경에서 사용하는 자바스크립트의 모듈은 ECMAScript Module이다. 사용방법은 아래와 같다.

node.js 환경에서도 ecmascripit module을 사용 할 수 있다. (단, 파일명을 mjs로 수정해야 한다.)

모듈을 만들고 실행해보면 위 이미지와같이 모듈의 정보가 나타난다. id는 무엇인지, exports된것이 있는지, 경로와 파일명은 무엇인지, 한번 로드 된적 있는지 등등
모듈 키워드에서 loaded 항목이 있었다. 모듈은 한번 로드되면 캐시로 남아서 다시 로드하지 않아도 된다. 한번 로드된 모듈은

이렇게 true로 뜬다.
모듈을 사용할 때 관련된 모듈들을 index.js에 저장하고, index.js를 통해서 export를 하는 방식이 있다.



그러나 이런 방식은 모듈을 복잡하게 하기 때문에 권장하지 않는다.

파일 주소가 달라지면 그냥 달라진 파일 주소를 입력해라!
이란, 오픈소스 Node.js 프로젝트 게시를 위한 온라인 repository(저장소)이자 패키지 설치, 관리 및 종속성(버전) 관리를 지원하는 명렁줄 유틸리티(명령어)로 온라인repository와 상호작용한다.
npm init -y(모두 yes)
npm install 모듈명npm install -g하면 전역적으로 설치.
설치시 이렇게 package.json에 현재 내가 설치한 depenencies의존성에 추가되고,
해당 디렉토리에 node_modules폴더가 생성된다. node_module에는 실제로 설치한 모듈과 그 모듈이 의존하고 있는 다른 모듈도 함께 설치가 된다.
이전 이미지에서, package.json파일을 보면 "axios" : "^1.6.8"과 같이 기록되어있는걸 볼 수 있다. 이것은 해당 패키지의 버전을 의미하는데, 각 자리는 모두 의미가 있다. 그래서 sementic version이다.
이전에 다른 게시글에서도 공부했기 때문에 각 자리의 의미만 가볍게 살펴보면, major.minor.patch를 의미한다.
major는 호환되지 않는 api / minor는 호환되지만 기능만 추가됨 / patch는 호환되면서 버그만 수정됐을 때 숫자가 증가한다.
~0.0.0 : 현재 지정한 버전의 patch범위에서만 자동으로 업데이트한다. 즉 ~0.0.0으로 작성되어있으면 ~0.0.9까지만 자동으로 업데이트한다.
^0.0.0 : 현재 지정한 버전의 minor범위에서만 자동으로 업데이트한다. 즉 ^0.9.0까지만 자동으로 업데이트한다.
어떤 모듈을 설치하고나면 해당 파일이 생성된다. 이 파일은 소스 레포지토리에 커밋하기 위한것으로 다양한 용도로 사용되는데, 버전이 캐럿이나 틸트로 적혀있는것이 아니라 현재 사용되고있는 버전을 정확하게 0.0.0으로 표시해준다. = 종석성 트리의 단일표현을 나타낸다.
이것이 왜 필요하냐면, 캐럿이나 틸트범위내에서 자동으로 업데이트가 되다보면 내가 공유한 파일을 받은 누군가와 내파일의 버전이 달라서 오류가 생길 위험을 방지하기 위함이다.
또 사용자가 디렉토리 자체를 커밋하지 않고도 node_modules의 이전상태로 돌아갈 수 있고, npm이 이전에 설치된 패키지에 대해 반복되는 메타데이터 확인을 건너 뛸수 있또록 하여 설치프로세스를 최적화한다.
프로젝트에 구성된 종속성(버전)에 대한 설명을 기본레지스트리에 제출하고, 알려진 취약성에 대한 보고서를 요청한다.
취약점이 발견되면 fix인수가 제공되어서 수정상항을 패키지트리에 적용할 수 있다.
-> 버전 업데이트가 되었는데 내 컴퓨터에는 그렇지 않을 수도 있으니까 그럴때 현재 버전에 취약점이 있는지를 확인하는것.
디렉토리 파일 변경이 감지되면 노드 응용 프로그램을 자동으로 다시 시작한다. node.js기반 응용프로그램을 개발하는데 도움이 되는 도구이다.
딱히 설정할것도 없다. nodemon으로 파일을 실행하면 파일이 수정할때마다 자동으로 해당 파일을 리로드 시켜준다.
(공부할때는 package.json의 script에 "dev" : "nodemon server.js"해서 사용했다. 보통 개발 버전에서 사용하기 때문이다. dev는 develop!
그래서 설치할 때도 개발용도로 사용하기 위해서 npm install -D nodemon으로 설치하는것이 좋다. nodemon 패키지가 프로덕션 환경에서는 설치되지 않고, devDependencies에만 추가된다. )
npm과 같은 패키지매니저로 npm의 보안과 성능이슈로 인해 페이스북에서 만들었다. npm install -g yarn (-g는 전역설치)으로 설치 할 수 있다.
npm은 패키지를 여러개의 패키지를 설치해야 할 때 동기적으로 동작하여 시간이 오래걸리지만, yarn은 비동기로 설치하기 때문에 시간이 빠르다.
yarn.lock파일도 생성된다. npm의 package-lock.json과 마찬가지로 최초 패키지 추가시의 버전이 들어가있게 된다. (패키지 버전 문제 최소화 가능)
yarn은 패키지를 다운로드 하는 동안 백그라운드 프로세스로 보안검사를 수행하여 패키지 라이센스 정보를 사용하여 악성 스크립트를 다운하거나 종속성 충돌을 일으키지 않도록 한다.
필요한 명령어는 그때그때 확인하는걸로 하고, 중요한건 npm과 yarn을 한프로젝트에서 동시에 사용하지 않는것이 좋다. 충돌오류가 발생할 수 있다.
웹서버는 텍스트, 이미지, 비디오 및 애플리케이션 데이터와 같은 웹사이트 컨텐츠를 요청하는 클라이언트(보편적으로 브라우저)에 전달한다. 즉 요청이 들어왔을 때 응답을 해줄 수 있는 창구를 열어두는 것이 서버다.

네트워크는 서로 다른 컴퓨터끼리 데이터를 주고 받는 것을 말하는데, 위에 도식화된 이미지와 같이 여러 계층을 거친다. 사용자와 가까운 계층을 상위계층, 0과 1로 소통하는 부분을 하위계층이라고 한다. 상위에서 하위계층으로 갈수록 전기신호에 가까운 데이터로 변경되어 소통한다고 생각하면 된다. http는 그 중 응용계층에 존재하는 프로토콜이다. 그래서 우리는 우리에게 익숙한 언어로 작성된 http를 볼 수 있는것이다.
웹서버는 HTTP를 사용하여 웹 브라우저와 통신한다.
좀 더 자세히 설명하면, 브라우저가 url을 dns서버(일반적으로 인터넷 서비스 제공업체나 기업, 조직, 인터넷 호스팅 서비스 업체 등이 자체적으로 운영하는 서버로 전 세계에 분산되어있음.)에 보내면, ip address를 응답받고, 이것을 web server에 보내면, 요청한 데이터를 제공받는다.
웹에서 이뤄지는 모든 데이터 교환의 기초이며 텍스트 기반의 통신 규약으로 인터넷에서 데이터를 주고받을 수 있는 프로토콜이다. 클라이언트-서버 프로토콜이라고도 한다.
즉 클라이언트와 서버가 존재하는것이 http의 특징이다. 클라이언트가 요청하면 서버가 응답한다.
요청과 응답 모두 http 메세지 형식으로 오고간다. 그런데 그 메세지가 작성하는 방법이 정해져 있다고 생각하면 된다. 작성하는 방법은 헤더:헤더값의 형태를 취한다. 이렇게 규약을 정해두었기 때문에 모든 프로그램이 이 규약에 맞춰 개발해서 서로 정보를 교환할 수 있게 되었다. 참조
stateless protocol : http는 정보 요청할 때 사용자의 정보를 보내지 않기 때문에, 서버는 사용자를 기억하지 한다. 그렇기 때문에 서버를 증설하는데 무리가 없다. 서버마다 기능이 모두 같다면 똑같은 요청을 하는 서버를 여러개 만들면 되기 때문이다. 요청이 올때마다 사용자를 확인해야하면 시간이 너무 오래걸려 성능이 떨어지기 때문이다. 정보를 저장하려면 별도의 인증절차가 필요해진다.
비연결성 : 서버는 응답을 하고나면 연결을 끊는다. 처음에 이렇게 동작한것은 데이터 통신을 위해 연결을 해놓기만해도 네트워크 비용이 발생하기 때문이다. 즉 비용적인 측면을 고려해서 만들어졌다. 그러나 시간이 지나면서 이것이 오히려 비효율적인 상황이 발생했다.
http1.0에서는 요청을 여러번 해야하면 연결하고, 요청하고, 응답하고, 종료하는 모든 과정이 동기적으로 이루어졌기 때문에 많은 시간이 소요됐다. 그것을 보안하기 위해서 http1.1에서는 응답마다 종료를 하지 않게 동작해서, 연결과 종료시간이 줄어들어 조금 더 효율적인 형태가 됐다. 하지만 요청과 응답이 아직 동기적으로 진행된다. 이것을 보안한게 파이프라인 형태로 요청은 한번에 받아오지만, 응답은 여전히 동기적으로 동작한다. http2.0에서는 응답과 요청이 모두 비동기적으로 동작하여 시간을 효율적으로 사용하게 됐다. (현재 http는 3.0이라고 한다!)
SOP : same-origin-policy 동일 출처 정책은 같은 출처의 리소스만 가져 올 수 있는것을 말한다. 클라이언트가 서버에 요청을 보내고 데이터를 받아 올 때 클라이언트와 서버가 같은곳에서 만들어지지 않았으면 통신을 할 수 없게 하는것이다. 외부 공격으로부터의 피해를 막기 위함이다.
같은 출처임을 판단하는 기준은 아래와 같다. 
그러나 sop 방식은 최근에 한계가 생겼다. api때문이다. 가장 흔하게 볼 수 있는 것은 소셜로그인이다. 요즘 어느 사이트에서나 구글로그인, 카카오로그인, 네이버로그인을 사용한다. 이렇게 외부사이트에 데이터를 요청하고 싶을 때 sop가 그것을 막는것이다.
CORS : cross-origin resource sharing 교차 출처 리소스 공유가 앞선 문제를 해결하는 방법이다.
어떤 주소, 어떤 메서드, 어떤 헤더의 요청은 허용하겠다는 위 이미지에 해당하는 헤더를 서버에 작성해준다. 
cors는 이렇게 클라이언트가 실제로 요청을 보내면 브라우저가 요청을 전달하기 전에 서버에 사전체크를 하고, 서버는 그것이 허용되는 접근인지 응답해준다. 그러면 그제서야 브라우저가 실제 요청을 전달하고 브라우저는 응답한다.
http request : 
스타트라인에 http, method, request.target, http version에 대한 정보를 담고있다.
headers에 key:value값으로 해당 request에 대한 추가 정보를 담고 있다.
body에 해당 request가 전송하는 데이터가 담겨있다.(POST 메소드의 경우)
http response :
status line에 http version, status code, status text를 나타낸다.
headers는 req와 동일하지만 res에서만 쓰이는 값도 있다.
body도 일반적으로 동일하다.
위 정보들은 개발자도구의 network탭에서 볼 수 있다.
const http = require('http'); 모듈 호출http.createServer(requestListener)로 서버 객체를 만들어줌. requestListener는 서버가 요청을 받을 때마다 요청과 응답에 대한 처리를 함.const server = http.createServer((req, res) => 함수) 이렇게 작성하면 (req, res) => 함수이게 requestListener임. server객체는 컴퓨터의 포트를 수신하고 요청이 만들어 질 때마다 함수를 실행 할 수 있음.server.listen(port, 콜백함수) : 서버 실행server.close() : 서버 종료
req가 가지는 메소드
res가 가지는 메소드예를들어, res.writeHead(200, {Content-Type : "text/plain"})하면 200status코드로 응답하고, 응답에 전달되는 콘텐츠의 유형이 텍스트라는걸 의미함
(그 다음에 res.write('해위') 라고 텍스트를 보낼것이다) / res.end("끗")는 응답이 완료되었다는 의미로, 메세지를 화면에 그릴 수 있음. (문자열만됨)
하나의 포트는 유지하면서 화면에 새로운 값을 보여주고 싶으면 라우팅 하면 됨. 조건문을 사용함.
위 이미지에서 보면 req.on <- on을 사용하고있음 on은 addeventlisnter너 같은거임.
http.IncomingMessage 및 http.ServerResponse 클래스의 인스턴스(를 상속받아 만들어진 것)이고 둘은 EventEmitter를 상속함으로 on을 사용 할 수 있음.
req.on(data, 함수)는 요청 데이터가 들어올때마다 그 데이터를 함수 처리하라 라는 의미. 주의할것은 받아온 데이터는 컴퓨터 언어임으로 문자열로 바꿔줘야 알아볼 수 있음.
새롭게 배워야 하는 개념이 아닌 지금까지 node.js express.js를 이용하면서 개발 할 때 이미 사용한 개념이다. 두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스로 REST Representional State Transfer는 인터넷과 같은 복잡한 네트워크에서 통신을 관리하기 위한 지침이다.
자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태 (정보)를 주고 받는 모든 것을 말한다. 자원이란 해당 소프트웨어가 관리하는 모든 그림, 동영상, 데이터를 말하며 자원의 상태란 자원의 정보를 말한다. REST는 기본적으로 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일이다.
HTTP URI를 통해 자원을 명시하고, HTTP Method (POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD OPERATION을 적용하는 것을 의미
이미 우리가 사용한 GET, POST 등이 이것이다. 값을 받아오는 요청은 get, 추가할 때는 post, 수정할때는 put을 써서 해야하고, 통신에 사용되는 url은 어떤식으로 작성해야 하는지에 대한 규칙이 REST이고 이것에 충실한것이 RESTful이다. 데이터 통신은 이렇게 작성된 RESTful api를 통해서 이뤄진다.
http://icecream.com/api/flavor/1웹 및 모바일 애플리케이션 구축을 위한 광범위한 기능을 제공하는 node.js의 웹 애플리케이션 프레임워크다.
node.js의 api를 단순화하고 유용한 기능은 더 추가시켜 서버와 경로를 관리하는데 도움이 된다.
굉장히 많은 사람이 사용 하고 있고, 사용법도 쉽다. 또 이것을 기반으로 만들어지는 다른 프레임워크들도 있어서 이것을 배우면 다른 프레임워크의 학습에도 도움이 된다.
npm install express 로 설치한다.const express = require('express') 모듈 다운const app = express() 서버생성app.get('/', (req,res)=>{
res.send("Hello World");
})app.get은 get방식으로 /경로에 요청이 오면 해당하는 응답을 보내겠다 라는 뜻이다. 여기서 눈여겨볼점은 응답할 때 http를 사용한 이전방식에서는 statuscode와 content-type을 명시해야 했지만, express는 그것을 알아서 처리해준다. 그래서 적혀있지 않다!app.listen(port, host) 해당 포트와 호스트에서 서버를 시작한다~!
params는 Express.js에서 라우트 경로의 동적인 부분을 추출하는 데 사용되는 속성이다. 라우트는 위에서 공부했지만, 하나의 포트는 유지하면서 화면에 새로운 값을 보여주고 싶을 때 사용하는 방식이었다. localhost:4000/home, localhost:4000/about에서 home과 about같이 요청에따라 달라지는 부분을 params라고 한다. express에서 params를 받아오는 방식이 위 코드이다.
예시에서 요청객체는 url정보를 담고있기 때문에 params의 정보도 가지고 있을것이다. 그 중 userId를 추출한다. (코드에서는 id값을 숫자로 사용했기때문에 변환과정을 거쳤다.)
그리고 그 params에 따라서 값이 존재하면 user(코드에서는 유저정보 객체가 담긴 배열이었다)로 응답하고, 존재하지않으면 응답코드 400으로 응답한다.
응답을 할 때 어쩔때는 send를쓰고 어쩔때는 json을 사용했다. 무슨 차이일까? 기능상으로는 거의 동일하다.
res.json() : 
결론적으로 obj를 응답해야 하는 경우에 send를 쓰면 불필요하게 과정이 추가될 수 있기 때문에 res.json을 쓰는것이 효율적이다.
응답이 있고 작업을 수행했다면 세션을 종료해야한다. (그러지 않으면 자원을 잡아먹고있을것이다.) 그러한 기능을 하는것이 res.end()이다. 하지만 사실 res.end()는 꼭 사용하지 않아도 된다. 데이터를 res.json()이나 res.send()로 보내면 알아서 종료되기 때문이다. 즉 res.end()는 데이터를 제공하지 않고 응답을 종료할 때 사용한다. (예. 404)
하지만 res.end(응답하는데이터)도 데이터를 보낼 수 있다. 브라우저에서 표기되는 것은 end나 send나 같다. 그러나 개발자도구에서 네트워크 -> 헤더를 살펴보면 다르다. end에는 content-type(왜냐면 무조건 문자열만 받는다고 했으니까), ETag(ETag HTTP 응답 헤더는 리소스의 특정 버전에 대한 식별자이다. 컨텐츠가 변경되지 않은 경우 웹서버에서는 전체 응답을 보낼 필요가 없음으로 캐시를 보다 효율적으로 사용 할 수 있다. 성능차이가 생긴다.)가 없다.
즉 컨텐츠를 보내야 할 때는 send를 쓰는것이 더 좋다.
개발한 api를 테스트하고, 테스트 결과를 공유하여 api개발의 생산성을 높여주는 플랫폼. 이라고 하니까 잘 이해가 안됐는데, 내가 node.js로 어떤 응답을 하는 프로그램을 만들었으면, 고객이 그것을 사용해줘야 제대로 동작하는지 알 수 있는데 그 요청을 하는 고객역할을 해주는 것이 포스트맨이다.
이렇게 통신 메소드, body, url을 선택해서 요청을 보내면 응답도 받아서 화면에 보여준다.

express는 자체 기능이 최소화된 라우팅 및 미들웨어 웹 프레임 워크다. 미들웨어는 익스프레스의 핵심적인 역할로 요청을 웹 서버에 보내면 서버가 그것을 처리 할 때 기능별로 정리된 미들웨어를 하나씩 거친 다음에 모든것을 처리하면 응답을 보낸다. express는 본질적으로 일련의 미들웨어 기능 호출이다. 
app.use()로 미들웨어를 등록하고, next()함수가 실행되면 다음 미들웨어로 이동한다. 위 이미지는 이해를 위해서 한뱡항으로 동작하는 것 처럼 보이지만, 실제로는
메인작업을 마치면 미들웨어들을 한번씩 거치고 응답한다.
express가 아닌 node.js에서는 요청이 get인지 post인지에 따라서 라우팅을 처리하려면 
이런식으로 작성해야하는데, express에서는 
.get() / .post()등의 메소드로 특정 URL에 대한 요청을 처리하고 응답을 생성할 수 있다. 위 코드는 /주소에서 get 메소드로 요청이 오면 콜백함수를 실행한다. (render는 이 뒤에 템플릿을 공부하면서 한번 더 나온다.)
내장 미들웨어 중 하나로, 요청 body(본문)에 포함된 JSON 데이터를 해석하고 파싱하는 데 사용된다. express.json()이 없었을 때는 bodyparser라는 모듈을 사용해야 했지만 익스프레스가 업데이트 되면서 대체 할 수 있게 됐다.
미들웨어이기 때문에 .use로 등록하면된다. 말그대로 등록만하면된다! 그러면 별도로 코드상에 내용을 추가하지 않아도 json을 알아서 해석해준다.
서버에 있는 파일을 클라이언트에 전송하고 싶을 때 사용한다. 보통 정적인 파일들은 public폴더에서 관리한다.

절대경로를 사용해서 경로를 입력해주기 위해서 path 모듈을 설치한다.(절대경로를 사용해야 하는 이유는 해당 파일을 실행할 때 상대경로로 입력되어 있으면, 다른 경로에서 해당 파일을 실행하면 경로가 변경될 수 있기 때문이다.) 사용방법은 위와 같다.
Express.js의 내장 미들웨어 중 하나이며, 정적 파일을 제공하는 데 사용된다. 이 미들웨어를 사용하면 Express 애플리케이션에서 정적 파일(예: HTML, CSS, JavaScript, 이미지 파일 등)을 제공할 수 있다.






Model-View-Controller의 약자로, 소프트웨어 디자인 패턴 중 하나이다. 소프트웨어를 세 가지 주요 부분으로 나누어 개발하는 방법을 제공한다.






여기서 추가로,바로 위 이미지와 같이 애플리케이션이 커질수록 api들이 많아져 (url이 많아짐) 복잡해질 수 있다. 이것들도 router로 따로 처리 할 수 있다.


express.static()으로 정적인 파일을 제공하는것을 공부했는데, 이것은 동적으로 값을 조작할 수 없다. mvc에 따라 작성했기 때문에 m=데이터가 바뀌면 v=뷰도 변해야 하는데 그렇지 않다. 이러한 작업을 할 수 있게 하는것이 template engine이다. 서버 측 애플리케이션에서 동적 html 페이지를 생성하는 방법을 제공한다.
여러 종류가 있지만 대부분 기능이 비슷함으로 하나를 배우면 다음 것도 익히기 쉽다. hbs를 이용해보겠다.

이 후 뷰 파일을 작성한다.
그리고 동적으로 입력할 값은 {{변수명}} 과 같이 중괄호 두개를 겹쳐 표시한다. (위 파일에서 경로에 static이 추가된 이유는, 위 실습에서 정적으로 표현하는 express.static()에서 경로앞에 /static을 추가해뒀음으로 해당 이미지와 css에 접근하려면 /static을 붙여야한다.)
.render() 메소드는 뷰 엔진을 설정하고 템플릿 파일을 클라이언트에게 보내기 전에 서버 측에서 해당 템플릿을 렌더링한다. 보통 HTTP 요청에 대한 응답으로 사용되며, .render() 메소드를 호출할 때는 렌더링할 템플릿 파일의 이름과 해당 템플릿에 전달할 데이터를 함께 전달한다. 위 코드는 /경로에 접근하면 index.hbs템플릿을 렌더링하고, 템플릿의 imageTitle에는 it is a2 laundry를 할당한다 라는 의미다.
실제로 접속해보면 잘된다!위 이미지에서 레이아웃이 존재하는 이유는 레이아웃을 만들어 줬기 때문이다. 화면에서 공통적으로 보여주고 싶은 부분을 보통 layout처리 한다. layout.hbs에
이렇게 작성한다. 고정될 내용은 코드로 작성하고, 주소에 따라 다르게 화면에 표시 될 부분은 중괄호 세개를 사용하여 표시한다.
index.hbs는 다음과 같이 수정한다. 그러면 이제부터 서버가 열린 포트 localhost:4000에 접속하면, layout틀이 생성되고, body에 index.hbs값을 보여준다. (chat GPT : 일반적으로 Express에서는 템플릿 엔진을 설정할 때 기본적으로 사용할 레이아웃 파일을 지정할 수 있습니다. 이 때 layout 속성을 사용하여 지정할 수 있습니다. 그러나 layout 속성을 따로 설정하지 않으면 Express는 views 폴더에 있는 layout.hbs 파일을 기본 레이아웃으로 사용합니다.)
추가로 만약에 localhost:4000/posts에 접근할 때 템플릿으로 화면을 그려주고 싶다면?
views 폴더에 posts.hbs를 다음과 같이 작성하고, render()도 설정해줘야하는데 아까 index는 특별한 경로가 없는 메인화면임으로 server.js에 작성했지만 posts는 라우터를 posts로 이미 설정해뒀기 때문에 

연결된 postsController에서 함수를 설정해주면 된다.
layout도 적용되고 {{{body}}}부분에 posts도 잘 노출된다.
index.hbs를 등록 할 때 처럼 server.js에 위와 같이 작성해도 괜찮지만 이렇게 페이지가 많아질때마다 코드가 복잡해지는 것을 방지하기 위해서 router폴더를 만들었으니 활용해준다.
여기서 어느정도 눈치를 챘는데! 바로 이것이 그 SSR이다. ٩(๑˃̵ᴗ˂̵)و 정확히는 SSR의 한 형태이다. SSR은 클라이언트에게 전송되기 전에 서버에서 HTML을 동적으로 생성하는 기술로 .render() 메소드를 사용하여 서버 측에서 템플릿을 렌더링하는 것은 SSR의 핵심 개념을 따르는 것이다. 클라이언트는 렌더링된 HTML을 받아와서 화면에 표시할 뿐, 서버 측에서 이미 완성된 HTML을 전달받기 때문에 추가적인 자바스크립트 실행이나 렌더링 과정이 필요하지 않다.
어떤 사이트에 들어가서 서비스를 이용하려면 보통 로그인 같은 인증을 요한다. 이것은 서버의 stateless한 특징 때문이다. 서버는 사용자의 정보를 저장해두지 않기 때문에, 사용자 입장에서 인증이 유지되게 하려면 절차를 거쳐야 한다.거기에 사용되는것이 토큰, 쿠키, 세션이다.
클라이언트에 저장되는 짧은 텍스트로 http의 무상태성statelesss을 보완한다. 만약 사용자 입장에서 로그인을 한번 했는데, 페이지를 이동하라고 할 때마다 로그인을 하라고하면 얼마나 열받겠는가!
간단한 메모와 같은 쿠키는 서버가 아닌 클라이언트에 저장된다. 서버가 모든 유저의 쿠키를 다 저장하고 있기에는 클라이언트의 수가 너무 많을수도 있고, 서버 증설에도 어려움이 있을 수 있기 때문이다.
서버는 응답에 쿠키를 추가해서 보내고, 클라이언트가 서버에 요청을 보낼때는 자동으로 요청에 쿠키가 담기게 된다. 클라이언트가 쿠키를 보낼지 말지를 선택할 수는 없다. 요청에는 무조건 쿠키가 담기게 된다. 즉 쿠키의 관리는 서버에서만 할 수 있다.

쿠키의 활용사례는 위와 같다. 어떤 페이지를 하루동안 보지 않는다고 체크하고 닫기를 누르면 서버는 해당 정보를 쿠키에 담아 클라이언트에게 저장해둔다. 이후에 클라이언트가 해당 페이지에 다시 접속할 때 보내지는 쿠키의 내용을 바탕으로 서버는 해당 페이지를 노출시키지 않는다.
혹은 어떤 제품을 검색하면 그것에 연관된 광고가 뜨는 경험이 한번쯤 있을것이다. 여기에도 쿠키가 사용된다. 쿠키에 내가 검색한 정보가 남아있어서 서버에서 광고를 띄울 때 그것과 관련된 광고를 노출 시키는 것이다.
이 말은 즉 외부사이트에서 저장된 쿠키라도 서버는 모두 받아 볼 수 있다는 것이다. 이것을 3자쿠키라고 한다.
( 곧 법이 재정되어 3자쿠키를 금지시킬 수도 있다고 한다. 그러면 아마 이런 광고가 사라지게 될 수도 있다. )
위 예시에서 볼 수 있듯이 쿠키는 보통 인증을 위해 사용되지는 않는다.
세션이란 사용자가 인증에 성공한 상태 혹은 연결이 끊어지기 전까지의 기간을 말한다.
세션 기반 인증이란 사용자가 인증에 성공한 상태를 서버에 저장해서 관리하는 방식이다.
클라이언트에서 로그인 요청을 보내면, 서버는 해당 유저가 존재하는지 세션 저장소에 확인한다.
세션저장소에 정보가 확인되면 서버는 응답에 쿠키를 담아 클라이언트에게 전달하고, 클라이언트측에서 세션은 쿠키에 저장된다.
이후에 인증 권한이 필요한 페이지에 클라이언트가 접속 요청을 보내면 자동으로 세션정보가 담긴 쿠키가 서버에 전송되고, 서버는 다시 유저의 정보를 확인한 후에 데이터를 전송한다. 이때 권한이 있으면 해당 페이지의 데이터가 전송되고, 아니라면 권한없음 오류 403을 응답할것이다.

세션 기반 인증 사례는 다음과 같다.
네이버와 같은 사이트는 컴퓨터에서 접속 할 수도 있고, 모바일이라도 모바일 앱인지, 모바일 웹인지 혹은 서브컴퓨터 등등 여기저기서 인증을 할 수 있다.
서버는 어느기기에서 어느시간에 접속했는지를 저장하고 있다. 아까 쿠키의 관리는 서버에서만 할 수 있다고 했다. 세션 정보도 쿠키에 저장되어 있기 때문에 서버는 해당 세션의 유효성을 삭제하여 강제로 로그아웃 시킬 수 있다. 그래서 위 이미지와 같이 원격 로그아웃이 가능한것이다.
혹은 좀 더 보안이 중요한 은행어플 같은 경우에는 세션에 유효시간을 줘서 시간이 지나면 자동으로 로그아웃 되게 만든다.
쿠키, 세션과 달리 토큰은 서버가 아닌 클라이언트가 소지하고 있는 인증 도구이다.
서버는 사용자로부터 유저 정보를 받으면, 그 유저정보를 포함한 토큰을 암호화하여 생성해서 응답헤더에 포함시켜 제공하고, 클라이언트는 쿠키나 로컬스토리지에 그것을 저장한다.
이후로 사이트를 이용하면서 권한(로그인 등)이 필요한 서비스에 접근하면 클라이언트는 토큰과 함께 요청을 보내고, 서버는 토큰을 복호화하여 유저정보를 보고 권한 여부에 따라 다른 정보를 제공한다.
가장 대표적으로 사용되는 토큰이 jwt이다. JSON 객체에 정보를 담고 이것을 토큰으로 암호화 하는 방식이다.
토큰은 헤더와 페이로드, 시그니쳐로 구성되어 있다.
헤더와 페이로드는 base64로 인코딩되고 인코딩된 값에 시크릿코드를 더하여 시그니쳐부분이 된다.(이때 base64인코딩 방식은 언제든지 디코딩 할 수 있음으로 payload에는 중요한 정보는 넣지 않아야 한다.)
즉 서버는 토큰 자체가 아니라 시크릿키만 가지고 있으면 언제든 해당 토큰을 다시 만들어 낼 수 있음으로 토큰을 서버에 저장할 필요가 없다.
토큰은 클라이언트측에서 관리하기 때문에 세션이나 쿠키와 달리 유출되면 서버에서 접속을 끊어낼 수 있는 방법이 없다.
아래에서 한번 더 공부하겠지만, 그렇기 때문에 유효기간이 짧은 엑세스토큰과, 엑세스토큰을 재발급하는데 사용될 상대적으로 유효기간이 더 긴 리프레시토큰 두개를 발급한다.
토큰은 보통 쿠키나 로컬스토리지에 저장된다.
요즘 많이 사용되는 소셜로그인에 사용되는 OAuth도 토큰 기반이다.

JSON Web Token은 당사자간의 정보를 JSON객체로 안전하게 전송하기 위한 컴팩트하고 독립적인 방식을 정의하는 개방형 표준이다. 해당 정보는 디지털 서명이 되어있음으로 신뢰할 수 있다.
간단하게 얘기하면 정보를 안전하게 전하고자 할 때 혹은 유저의 권한을 체크하고자 할 때 사용하는 토큰을 생성하는데 유용한 모듈이다.

jwt의 구조는 위의 이미지와 같이 3개의 segment가 . 으로 구분되어 있다.

npm init -y : package.json 생성
npm install dotenv express jsonwebtoken nemon : 필요한 모듈설치(코드에 사용된것은 환경변수생성을 위한 dotenv와 토큰 생성에 사용되는 jsonwebtoken, 나머지 두개는 앞에서 공부함)
엔트리파일로 사용할 index.js 생성 (앤트리파일이란 프로그램이나 애플리케이션의 시작점이 되는 파일을 가리킵니다. 일반적으로 웹 개발에서는 주로 JavaScript로 작성된 파일을 말한다.)
express 앱을 만들고 포트에서 서버를 시작한다. 로그인 요청은 유저에 대한 정보를 담기 때문에 post 메소드를 사용하고, post요청의 body는 객체이기 때문에 이것을 파싱하기 위해 express.json미들웨어를 등록한다.
로그인 api를 생성한다.
post메소드로 /login url을 통해 요청이 오면, body의 username을 user에 담고, user와 미리 생성해둔 secertText를 jwt.sign(키를만들값, 키, 알고리즘)메소드를 통해서 생성한다. 그리고 클라이언트에게 토큰을 응답한다.
포스트맨에서 토큰이 잘 생성된걸 확인 할 수 있다.
로그인에 성공하고 나면, 토큰으로 게시글을 요청하는 클라이언트에게 게시글을 보낸다.

값이 잘 받아지는 모습
잘동작하면, 토큰이 유효한지 확인하고 클라이언트에게 게시글을 응답하도록 수정한다.
authMiddleware는 토큰을 받아서 유효한지 확인하는 미들웨어다. 토큰은 요청의 헤더에 담겨서 온다.
포스트맨에서는 이런식으로 요청에 토큰을 담아서 보낼 수 있고, Bearer zasfsafd.afwefasdf.ggaega과 같은 형태임으로 split하면 [beraerer, 230loKNeflkwnefawelmf]이런형태의 배열이 된다.
const token = authHeader && authHeader.split(' ')[1]; 이 코드는 조건문으로 authHeader가 존재하고 빈 문자열이 아니면 뒤의 내용을 실행한다.
만약에 토큰이 존재하지 않으면 인증이 되지 않으니 401오류상태를 반환하고 미들웨어를 끝내고, 그렇지 않으면 다음줄을 실행한다.
jwt.verify는 토큰을 확인하는 메소드이다. 받은 토큰을 시크릿키를 이용하여 검증하고, 틀리면 err를 반환하고 미들웨어를 끝낸다.
맞으면 토큰을 디코드하여 페이로드(값이 담긴 곳) 부분을 user에 담는다. (위 코드에서는 username이 나온다.)
그리고 다음 미들웨어에서 디코드된 페이로드를 사용할 수 있도록 req.user = user해서 담고 다음 미들웨어로 넘어간다.= next()
sendStatus()는 express에서 사용되는 메서드로 HTTP 응답 상태 코드를 설정하고 해당 상태 코드에 대한 표준 메시지를 응답으로 전송한다.
이렇게 만들어진 미들웨어는 라우터에서 사용 할 수 있음으로 등록하면, 해당 미들웨어를 통과(next()까지 코드가 진행되어야)해야 그 다음에 존재하는 이벤트 핸들러가 실행되게 동작한다.

요청해보면 잘 동작한다.(위 코드에서는 아직 토큰을 쿠키나 로컬스토리지에 저장하지 않아서 이 전에 받아온 토큰을 복사해서 붙여넣었다.)
accesstoken의 단점은 한번 발행하면 그것을 가지고 계속해서 인증이 필요한 요청을 보낼 수 있다는 점이다. 로그인을 한번 더 해서 토큰을 받아도 그 이전에 사용한 토큰은 유효하다.
기존의 방식은 하나의 토큰을 탈취하면 그것으로 계속 요청을 보낼 수 있어서 이걸 방지하기 위해 토큰에 유효시간을 줄 수 있다. 토큰의 유효시간을 사용하면 보안은 좋아지지만 사용자 입장에서는 유효기간이 끝날 때 마다 로그인을 해줘야 하니까 번거러울 수 있다.
위와 같은 문제를 보완하기 위해 고안된것이 refreshtoken이다. accesstoken과 같이 jwt를 통해 발급한다. accesstoken의 유효기간은 상대적으로 짧게 설정하고, refreshtoken의 유효시간은 길게 설정한다.

서버는 처음 클라이언트의 인증 요청이 유효하면 accesstoken과 refresh토큰을 모두 발급하고, 클라이언트는 그것을 캐시나 로컬스토리지에 저장한다. 다시 클라이언트가 access토큰으로 요청을 보내면 서버는 protected된 자원을 제공한다.
이후 클라이언트가 다시 요청했을 때 access토큰의 유효시간이 끝나면 서버는 invalid(유효하지않은) token error를 전송하고, 클라이언트는 다시 리프레시 토큰으로 요청을 보낸다. 서버는 refresh토큰이 유효(기간과 동일성)한지 확인하고, accesstoken을 다시 만들어서 클라이언트에게 보내면, 다시 첫 응답부터 반복된다!
실제로 만들어보기

포스트 메소드로 요청을 받으면 토큰을 발급하던 코드에 추가로 입력했다. asseccToken에 유효기간을 추가하고, refreshSecretText를 이용해서 refreshtoken을 생성한다. 유효기간은 더 길게 설정한다. refreshSecretText는 원래 db에서 관리하지만, 실습환경에서는 배열에 저장했다.
이후 토큰을 쿠키에 저장하기 위해서 res.cookie(키, 값) 메소드를 사용했다. httpoOnly는 쿠키의 프로퍼티로 javascript를 통해서 캐시에 담긴 토큰을 탈취하거나 조작할 수 없게 해준다.(XSS Cross Site Scripting 공격)
설정하지 않으면 이렇게 콘솔에서도 쉽게 캐시를 확인 할 수 있다. maxAge는 쿠키의 유효 시간을 설정한다.(시간이 지나면 쿠키에서 사라짐)
포스트맨에서 요청을 보내고 쿠키를 보면 잘 저장되어있다. 유효시간인 30초후에 해당 토큰으로 겟요청을 보내면, 403 오류코드로 응답한다.
이렇게 access토큰이 완료되면 refresh토큰으로 요청을 보낼때는 /refresh 주소로 보내도록 한다. 
그런데 확인해보면 쿠키에 들어있는 값이 확인되지 않는다. body의 값을 사용하려면 express.json을 사용해야 했던 것처럼, 쿠키도 파싱작업이 필요함으로 cookie-parser 모듈을 설치하고 import하고 미들웨어를 등록한다.

잘 동작하는 모습!
이후 쿠키를 가져와서 쿠키가 유효하지 않으면 401 오류코드를 보낸다. (위 코드는 오타다.)
!cookies?.jwt는 옵셔널체이닝 문법이다. cookies와 .jwt가 존재하는지 확인한다. cookies가 존재하고 cookies.jwt가 존재하면 cookies.jwt를 반환하고, 둘 중 하나라도 존재하지 않으면 undefined를 반환한다.
refreshtoken이 존재하면 db와 확인해서 값이 일치하는지 확인한다. 일치하면 verify를 사용해서 유효성을 검사하고 토큰에 있는 payload를 사용해서(어차피 처음 access, refresh 만들때 같은 user를 사용했으니까 다시 access를 만들 때도 refresh에서 추출하면 같은값임) 새로운 access코드를 만들어서 응답한다.
(마지막에 유효성 검사하는부분은 사실 db와 다르면 끝아닌가? 굳이 유효성 검사를 해야하나? 싶었는데 생각해보니까 유효성 검사를 해야 그 안에 든 payload를 추출할 수 있더라 ㅇㅅㅇㅋㅋ 그래서 유효성 검사의 의미보다는 그 안에 든 payload로 새로운 토큰을 발행하기 위한 과정에 더 의미가 크다는 느낌을 받았다.)
노드 js로 인증 / 인가를 구현 할 때 앞에서 한 방식도 좋지만, passport라는 모듈을 사용하면 훨씬 깔끔하게 구현 할 수 있다.
passport는 session을 사용하는 미들웨어이다. 세션 역시 http의 stateless한 특성 때문에 만들어진 인증 방식이다.
passport를 이용해서 회원가입시 db에 이메일과 비밀번호를 저장하고, 아이디와 비밀번호를 db와 비교해 로그인을 하는 방식과 구글 아이디를 이용하는 소셜 로그인을 구현해보자.
프로젝트 폴더 생성
npm init -y
모듈 설치 : npm i dotenv express nodemon cookie-parser cors mongoose passport passport-local passport-google-oauth20
(각 모듈은 직접 사용하면서 설명한다!)
이번에는 소스를 관리할 src폴더를 생성하고, 그 안에 엔트리파일로 server.js를 생성한다.
!
express를 호출하고, express를 생성하고 body parser역할을 할 express.json() 미들웨어를 미리 등록한다.
두번째 use는 front에서 form을 통해 데이터를 전달 받을 때
form 내부를 파싱하기 위한 설정으로 vaule를 받아오기 위함이다.
그리고 서버를 시작한다!
이번에는 db에 user정보를 저장할거라서 mongoose 모듈을 사용한다.
with chat GPT
- mongoose : Mongoose는 MongoDB와 함께 사용되는 Node.js의 객체 모델링 도구이다. 이 도구를 사용하면 MongoDB 데이터베이스에서 JavaScript 객체를 정의하고 조작할 수 있다. Mongoose는 MongoDB의 스키마 기반 접근 방식을 사용하여 데이터 모델을 정의하고 데이터 유효성을 검사하는 기능을 제공한다.
- mongoDB : NoSQL 데이터베이스 시스템 중 하나로, 문서 지향(Document-Oriented) 데이터베이스이다. 관계형 데이터베이스와 달리 몽고디비는 JSON과 비슷한 BSON(Binary JSON) 형식의 문서를 사용하여 데이터를 저장한다. 또 확장성이 좋아 모바일이나 앱 모두에서 사용 가능하다.
- 스키마 :
"스키마(Schema)"는 데이터베이스에서 데이터의 구조를 정의하는 개념이다. 이는 해당 데이터베이스에 저장되는 데이터의 형식, 구조, 제약 조건 등을 명시적으로 정의하는 것을 말한다. 스키마는 데이터베이스 테이블, 컬렉션, 문서 등에 대한 설계도라고 생각할 수 있다.

실습을 위해 몽고 atlas에 접속하여 클러스터를 생성했다.
connect -> drivers 에 들어가면 mongodb+srv://<username>:<password>@cluster0.sfgsqpg.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 이런식으로 연결하는 코드를 준다. 클러스터를 생성할 때 사용한 username과 비밀번호를 넣어주면 된다.


server.js로 돌아와서 mongoose.connect(mongodb+srv://<username>:<password>@cluster0.sfgsqpg.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0)로 서버와 db를 연결한다.
(.connect()메소드는 프로미스를 반환함으로 실행이 완료되면 .then().catch()로 값을 받아와 처리한다.)
실행 후 경고가 뜨면 mongoose.set('strictQuery', false);를 추가한다. (엄격성을 조절하는 세팅이라고 한다.)
+) mongoose를 require하는 모습이 조금 낯선데, vscode에서 모듈을 사용할 때 자동으로 추가해준 모습이다. 우리에게 익숙한 코드로 수정해도 정상적으로 동작한다. 이미지의 모습은 default로 설정해주는 것이라고 한다.
이렇게 하면 db연결은 끝난다. 매우 간단!
model 폴더를 생성하여 user데이터와 스키마를 작성한다.!
(스키마 작성법 주소!)
위 코드에서 각 데이터의 타입은 문자열이다.
unique: true는 유일값으로 다른 데이터와 같은 값을 가지지 못하게 하는 속성이다.
sparse는 앞으로 구현할 구글로그인을 고려하여 넣은 속성이다. 이메일과 패스워드로 로그인을 하면 구글아이디 필드는 null이 된다. unique한 값으로 사용하는데 null이 여러개가 되면 오류가 발생하고 그것을 방지하는 것이 sparse 속성이다.
.model()메소드는 mongoose에서 모델을 정의하는데 사용된다. 모델은 스키마를 통해서 인스턴스를 만들고 해당 모델을 사용하여 데이터 베이스 작업을 수행 할 수 있다.
해당 코드는 위에서 설정한 스키마를 베이스로 User라는 모델을 만들었다. User객체는 인스턴스이기 때문에 아래 이미지와 같이 db를 조작할 수 있는 메소드를 상속받아 가지고있다.
회원가입을 위한 화면을 서버에서 구현한다. 이번에 사용할 엔진은 ejs이다.
npm i ejs
label과 input이 잘 연결되어있고 각 input에는 name이 기입되어 있어서 서버에서 그것을 구별 할 수 있다.
다시 server.js로 돌아가서 라우터에 render해주면 해당 주소로 접속 할 때 설정된 ejs파일을 화면에 띄운다.
회원가입 기능 구현
signup.ejs에서 post메소드로 /signup경로에 요청을 보내도록 설정했었다. 그래서 요청을 받을때도 그렇게 작성한다. 요청을 받으면 서버는 email과 password가 담긴 body를 아까 만들어둔 User모델에 담아서 db에 저장한다.
try / catch는 예외처리를 위해 사용된다. try블록 내에 첫 줄에서 예외가 발생하면 둘째줄은 실행되지 않고 catch로 넘어가서 오류로그를 보여준다.
.save()메소드는 db의 users컬렉션에 user를 저장한다.
users 커렉션은 언제 생성되는거지?! with chatGPT
/signup 엔드포인트를 통해 새로운 사용자가 가입할 때마다 User 모델에 새로운 사용자가 저장됩니다.
MongoDB에서는 Mongoose를 사용하여 데이터를 저장할 때, 모델의 이름을 복수형으로 변환한 이름의 컬렉션을 자동으로 생성합니다.
따라서 User 모델을 사용하여 새로운 사용자를 저장할 때, MongoDB는 자동으로 users 컬렉션을 생성하고 거기에 사용자 문서를 저장합니다. 이것이 MongoDB에서 users 컬렉션이 생성되는 이유입니다.
또 .save()메소드는 비동기로 동작함으로, await을 사용하여 db에 값이 저장 될 때까지 기다린 후 다음 코드를 실행하도록 한다.

signup 화면에서 값을 입력하고 제출을 누르면 응답이 잘오고 db에도 저장이 된것을 확인 할 수 있다.
로그인 기능 만들기
로그인 요청은 post메소드로 /login경로로 서버에 온다. 이제까지는 passport모듈을 사용하지 않았는데, 여기서부터 겁나게 사용된다. 갱장히 복잡해서.. 흐름을 잘 정리 할 수 있을지 조금 걱정된다 ( -̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥᷄ _ -̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥᷅ )
passport를 사용하기 전 server.js에 다음과 같은 설정을 해야한다.
요청을 받을 때 마다 실행되는 passport가 초기화 되도록 하고, 앞으로 session에 사용자 정보를 저장할거라서 session미들웨어도 등록한다.
초기화는 express와 passport를 통합하는 과정이라고 한다. session은 passport가 생성하는데, res, req에 값은 express가 집어넣음으로 값을 넣을때 session도 넣으려면 통합과정이 필요한걸까? 라고 추측했다. 왜 추측했냐면 해당 내용을 구글링으로 못찾았기 때문이다 ㅎ_ㅎ 그래서 해당 과정이 있어야 sesssion을 사용 할 수 있음으로 두 코드는 단짝이다.
처음에 로그인을 할 때 아이디,비밀번호로 로그인 하는 것과 구글 로그인 두개를 구현하겠다고 했는데 지금 구현할 것은 아이디, 비밀번호로 로그인하는 localStrategy이다. 
passport.authenticate는 Node.js 웹 애플리케이션에서 사용자의 인증을 처리하는 Passport 미들웨어의 종류이다.
주로 라우팅 핸들러에서 사용되며, 사용자의 인증을 처리하고 인증된 사용자에 대한 처리를 수행한다.
위 코드에서는 local전략을 사용하여 요청에 담긴 password를 검증한다.
9.1 로컬전략만들기
프로젝트 폴더에 config폴더를 생성한 후 passport.js파일을 생성한다.
필요한 모듈을 호출한다. 여기서 passport-local이 사용된다.


새로운 localstrategy를 생성해주는데, 그 값으로 객체를 파라미터로 넣었다. (전략은 이번 버전이 되면서 반드시 promise로 작성하도록 변경되었다고 한다.)
첫번째 객체는 user의 정보이다. 원래 mongoose는 username을 받지만, 우리는 email을 사용 했기 때문에 usernameField:'email' 이라고 기입한다. (req.body.email 이런식으로 값을 받아 올 것이다.)
그러면 핸들러함수는 생성된 email, password와 done함수를 받아서 실행한다.
done()메소드 with chatGPT
Passport.js를 사용하여 로그인 인증을 처리하는 경우, 인증 전략(strategy)을 정의하고 해당 전략에 따라 사용자를 인증하는 과정에서 done() 함수를 호출하여 결과를 처리합니다. 일반적으로 done() 함수는 두 가지 인자를 받습니다.
첫 번째 인자(error): 인증 과정 중에 오류가 발생한 경우 해당 오류를 전달합니다. 오류가 발생하지 않은 경우에는 null을 전달합니다.
두 번째 인자(user): 인증이 성공했을 경우에는 해당 사용자 객체를 전달하고, 실패했을 경우에는 false를 전달합니다.
그리고 그 결과를 이후의 미들웨어에 전달합니다.
핸들러함수는 이메일이 데이터베이스에 존재하는지 확인한다. 앞에서 User모델은 생성된 인스턴스로서 db를 조작 할 수 있는 메소드가 있다고 했다. 그것을 활용하여 User.findOne메소드를 사용해서 email이 db에 존재하는지 탐색한다. 탐색을 마치면 그 값을 user로 전달한다.
만약 user가 존재하지 않으면 done(오류없음, 근데 유저도 없음, 메시지를 전달함)함수가 실행되어 다음 미들웨어로 정보를 넘긴다.
user가 존재하면 comparePassword함수를 실행한다.
comparePassword함수는 스키마의 메소드로 등록해서 사용함으로 user.model.js파일에서 추가한다.
위 코드는 비밀번호를 암호화 하지않고 사용자의 비밀번호인 plainpasword와 콜백함수cb를 받아서 db에 존재하는 패스워드(=this.password. this가 db인 이유는 User가 될꺼니까! User모델은 db를 볼 수 있으니까!)와 동일한지 검사한다.
그리고 맞으면 null, true를 다음 콜백함수에 전달하고, 틀리면 null,false를 다음 콜백함수에 전달한다. 에러가 있으면 에러를 콜백함수에 전달한다
콜백함수는 로컬전략에 있는(err, isMatch)=>{}이다. 다시 로컬 전략으로 넘어와서,
만약에 err가 있으면 done(err)로 다음 미들웨어에 에러로그를 넘긴다.
isMatch가 true이면, 비밀번호가 같았다는 의미니까 done(오류없음, user)로 다음 미들웨어에 user정보를 넘긴다.
에러는 없지만 isMatch가 false이면 done(null, false, 오류메시지)를 실행하여 다음 미들웨어로 정보를 넘긴다.
passport.use('local', localStrategyConfig);로 패스포트에 로컬전략 미들웨어를 등록한다.
이제 다시 server.js로 돌아가서, 라우터에 전략을 마저 등록한다.
(코드를 분석하기 전에 해당 코드는 미들웨어 안에 미들웨어가 존재하는 방식으로 작성되어서 내부에 있는 미들웨어가 동작하기 위해서 마지막 부분에 (req, res, next);를 추가해 준 것이다. 잘 모르겠지만 강의에서 그렇다고 한다!)
요청이 실행되면 핸들러 함수가 실행되고 핸들러 함수는 받은 정보로 passport.authenticate미들웨어를 실행한다.
passport.authenticate미들웨어는 아까 작성한 로컬전략을 실행한다. 로컬전략이 실행되면 결과에따라 done()함수가 값을 다음 미들웨어에 전달한다고 했는데, 그 전달받는 미들웨어가 'local'옆에 작성된 핸들러함수 (err, user, info)=>{}이다.
즉 (err, user, info)=>{}는 done에게 값을 받아서 함수를 실행한다.
err에 값이 존재하면 첫번째 if문을 실행하여 다음 미들웨어로 err를 전달한다.
err는 없지만 user의 정보도 틀려서 false(isMatch)를 받았으면 user의 정보가 틀렸다는 메시지를 응답한다.
err도 없고 user정보가 맞아서 true이면 req.login 함수를 실행한다. req.login은 passport에서 제공하는 메소드로 세션에 사용자 정보를 저장한다. 오류가 있으면 오류를 다음 미들웨어로 넘긴다.
코드에서는 오류가 없다면 로그인이 완료되었으니 로그인페이지를 벗어나서 '/'경로로 redirect 한다.
큭큭..이제 반왔다..res.login은 정보를 세션에 저장한다고 했는데, 그 세션에 저장할 때 어떻게 저장되어야 하는지도 정해줘야한다. 그것이 다음 부분이다.
로그인 세션 생성하기
res.login메소드가 호출되면 세션에 회원정보가 저장되는데, 그 세션은 serializeUser 메소드를 통해 생성된다.
passport.js에 해당 부분을 작성한다. user는 req.login에서 받은 user값이다.

serializeUser with chatGPT
serializeUser는 Passport.js에서 사용되는 함수 중 하나로, 사용자 객체를 세션에 저장할 때 호출되는 콜백 함수입니다.
Passport.js에서 세션을 사용하여 사용자 인증을 유지할 때, 사용자 정보를 세션에 저장해야 합니다. 그러나 사용자 정보는 대개 크고 복잡한 객체일 수 있으며, 세션에 직접 저장하기에는 너무 많은 공간을 차지할 수 있습니다. 또한, 모든 사용자 정보를 세션에 저장하는 것은 보안상의 이유로 권장되지 않습니다.
그래서 Passport.js는 사용자 정보를 세션에 저장할 때 사용자 객체를 직렬화하여 저장하는 방식을 채택하고 있습니다. serializeUser 함수는 이 과정에서 호출되며, 사용자 객체를 세션에 저장 가능한 형태로 변환합니다. 일반적으로는 사용자의 고유 식별자 (예: 사용자 ID)를 세션에 저장합니다. 이렇게 함으로써 세션의 크기를 줄이고 보안을 강화할 수 있습니다.
그리고 유저가 그 상태로 페이지에 접속 해서 요청이 올 때마다 세션은 쿠키에 담겨서 함께 전송이되고, 그럴때마다 deserializeUser메소드가 호출된다. deserializeUser메소드는 serializeUser에서 사용된 id를 이용해서 db에서 유저를 찾아 유저의 모든 정보를 가져온다.
그러고 나면(then) done(null, user)가 실행된다. 이 함수는 req.user에 user를 담아서 서버의 다른 코드에서도 사용 할 수 있게 한다.
(done이 그냥 다음 미들웨어로 넘겨준다 라고 생각했는데 여기서는 아무리 봐도 다음 미들웨어가 없는거같아서 생각해보니 passport는 이미 누군가 정해둔 방식을 내가 배껴와서 쓰는거니까 내부적으로 req.user를 만들어주는 함수가 있는거 아닐까싶다.)
쿠키세션설정
세션을 관리하는 방법으로 cookie-session과 express-session이 있다.
쿠키 세션은 클레이언트에만 세션을 보관하는것이고, 익스프레스 세션은 클라이언트에는 세션 식별자만 저장하고 데이터베이스에 세션을 저장하는 방식이다.
익스프레스세션은 보안적으로 더 뛰어나지만, 서버가 여러개일 경우 다른서버에 요청이 가면 오류가 날 수 있다는 문제가 있다. 이것을 해결해려면 추가로 여러 설정을 해야해서 이 장에서는 쿠키세션을 이용한다.
쿠키 세션이 동작하는 흐름은 위 이미지와 같다. 앞에서 한 과정이다!
처음에 요청이 들어오면 서버가 응답하여 클라이언트의 쿠키에 세션이 저장된다. 그 후에 다시 요청이 오면 서버는 쿠키데이터를 추출하고, id를 사용하여 db에서 해당하는 user의 정보를 가져와서 req.user에 등록한다.
세션을 쿠키에 저장하기 위해서 npm i cookie-session 모듈을 설치하고 require한다.
이후에 토큰에서처럼 시크릿키를 생성하고, 쿠키세션 미들웨어를 익스프레스에 등록만 해주면 된다!(아주간단)
코드에서 확인할 수 있듯이 쿠키세션의 키는 배열의 형태다. name을 따로 기입하지 않은면 기본값으로 express:sess로 등록한다.
이 부분 할 때 아주 그냥 오류만 5시간동안 찾았다..
Error: Login sessions require session support. Did you forget to use express-session middleware?이런 오류로그가 계속 화면에 출력됐다.
어떻게 해결 했냐면이미지 처럼 코드 작성 순서를 바꿧다.
기존 코드에서는 아래 두줄이 더 위에 있었다. 그랬더니 세션을 어디에 등록할지 정하지도 않았는데 세션을 패스포트에 등록하라고 하니 계속 오류가 났던것이다.. 그래서 뭣도 모르는 익스프레스는 자꾸 나한테 express-session이라도 쓰라고 화면에 출력해준것이다 ㅜㅜ.. 힌트는 이 주소에서 찾았다.. 순서를 바꿔보시오 라고..
추가로 코드를 실행해보면 pending상태에서 통신이 멈춰있다. 패스포트 6.0이랑 쿠키세션을 같이 쓸때 나오는 문제라고한다. server.js에 아래 코드를 추가하면 해결된다.
// register regenerate & save after the cookieSession middleware initialization app.use(function (request, response, next) { if (request.session && !request.session.regenerate) { request.session.regenerate = (cb) => { cb(); }; } if (request.session && !request.session.save) { request.session.save = (cb) => { cb(); }; } next(); });
여기까지 하고 로그인화면에서 아까 가입한 내용을 기반으로 로그인해보면
이렇게 화면이 잘 넘어간다. 아직 index.js를 만들어 주지 않아서 파일을 받을 수 없다고 뜨는것이다.

바로 만들어주고 라우터에 추가해준다.
그러면 화면에 잘 뜬다!
인증을 위한 미들웨어 생성
웹사이트에 있는 여러 페이지중에 로그인 인증이 된 사람만 볼 수 있는 페이지가 있을것이다. 예를들어, 로그인페이지나 회원가입 페이지는 인증이 되어있다면 보여질 필요가 없다.
이럴 때 인증을 위한 미들웨어를 생성하여 라우터에 등록한다.
미들웨어 폴더를 만들어서 만들어주고, server.js에서 require 한다.
라우터에 등록해준다. req.isAuthenticated()는 passport에서 제공하는 메소드로 현재 요청이 인증된 사용자에 의해 만들어진 것이면 true를 반환한다.
즉 /경로에서는 인증이 되어 있으면 그대로 두고, 아니라면 로그인 페이지로 이동시킨다.
/signup, /login경로에서는 인증이 되어있으면 /경로로 바로 이동시킨다.
로그아웃구현
로그아웃은 passport에서 제공하는 logout()메소드를 사용하면 간단하게 구현 할 수 있다.

passport logout으로 검색하면 나오는 사용법이다. get요청보다 post나 delete요청을 사용하라고 권장한다.
요청은 이미 index.js에서 구현했다.
server.js에 코드를 추가하기만 하면 된다.
그러면 로그아웃 버튼을 누르고 나면 세션이 바뀌면서 인증이 끝난것을 볼 수 있다.
비밀번호 암호화
우리는 회원가입 단계에서 db에 비밀번호를 저장할 때 암호화 하는 과정 없이 그대로 사용했는데 이것은 최악의 방법이라고 한다 ㅋㅋ!
비밀번호를 암호화하는 여러가지 방법이 있다.
실제로 해보장
우선 bcrpyt 모듈을 설치한다. 그리고 아래 코드를 추가한다.
해당 과정은 users.model.js에서 작성한다. 코드에 적힌 그대로 db에 문서가 저장되기 전에 동작할 스키마를 하나 더 추가한다.
this는 회원가입 할 때 받은 user를 가리킨다. 즉 처음에 user가 입력한 password를 변수 user에 담는다.
참조 : isModified() 라는 함수는 mongoose 모듈에 포함되어있는 함수입니다.
파라미터로 들어온 값이 db에 기록된 값과 비교해서 변경된 경우는 true를, 그렇지 않은 경우는 false를 반환하는 함수입니다.
user 생성 시에는 항상 true이며, user가 수정되는 경우에는 password가 변경되는 경우에만 true를 반환합니다.
user.password의 변경이 없는 경우라면 이미 해당 위치에 hash가 저장되어있으므로 false를 반환해 다시 암호화를 하지 않습니다.
현재 생성한 userSchema가 mongoose를 활용해서 생성한 schema이기 때문에 mongoose 모듈에 포함되어있는 isModified() 를 사용할 수 있다고 이해하시면 될 것 같습니다.
즉 지금은 user를 처음 생성했으니까 isModefied는 true가 되고 내부의 함수를 실행시킨다.
bcrpyt.gensalt()는 salt의 길이를 숫자로 받고(지정하지 않아도 기본은 10) 다음 함수를 실행한다.
에러가 있으면 에러를 다음 미들웨어로 보낸다.
그렇지 않으면 받은 user.password와 생성된 salt를 이용하여 hash를 생성하고, 에러가 있으면 에러를 보내고, 그렇지 않으면 생성된 hash를 userpassword로 저장하고 다음 미들웨어를 호출한다.
여기까지 하고 다시 회원가입을 해보면
db에 비밀번호가 암호화 되어 잘 저장된 모습이다!
앞에서 아이디와 비밀번호를 이용해 로그인 / 회원가입 / 로그아웃 하는 방법을 공부했다.
이번에는 소셜로그인이라고 하는 Oauth를 사용하여 로그인을 구현해보자.

위 이미지는 번개장터에서 로그인 버튼을 누르면 나오는 로그인 모달이다. 번개장터를 사용하려면 굳이 회원가입 하지 않아도 이미 계정이 존재하는 다른 서비스에서 계정정보를 가져오면 된다.
이렇게 이미 사용자정보를 가지고 있는 서비스가 다른 서비스의 사용자인증을 대신하는 것이 OAuth이다.
OAuth를 이용하면 사용자의 정보를 입력하는 과정 없이 이미 가입되어 있는 서비스에서 인증 후 내 정보를 대신해서 보내준다.
이때 사용자의 정보를 Resource라고하고, 사용자는 resource owner라고한다.
OAuth를 이용하는 서비스는 application이고, client, server로 구별하기도 한다.
resource를 가지고 있는 구글, 네이버와 같은 서비스를 resource server 혹은 authorization server라고 한다.
클라이언트와 서버를 구분한 인증 흐름은 위 이미지와 같다.
사용자가 애플리케이션에 접속하면 클라이언트는 rauthorization server에 인증요청을 보낸다.
authorization server는 사용자를 확인하고 인증코드를 응답한다. 응답을 받은 클라이언트는 서버에 코드와 함께 요청을 보낸다.
요청을 받은 서버는 authorization server에 인증코드와 함께 요청을 보내고, resource server는 인증코드의 유효성이 확인되면 엑세스 토큰을 발급한다.
엑세스 토큰을 받은 서버는 resource server에 엑세스 토큰과 함께 사용자 정보를 요청하고, 토큰이 올바른지 확인되면 resource server는 사용자 정보를 서버에 제공한다.
사용자 정보를 받은 서버는 클라이언트에게 사용자정보를 응답하고, 클라이언트는 사용자정보를 사용하여 화면을 그린다.
Google OAuth Key 발급받기




구현
(앞에서 클라이언트와 서버가 나누어져있는 환경에서 구글OAuth가 이루어지는 흐름을 살펴보았지만, 실습에서는 nodejs 서버 환경에서 소셜로그인이 이뤄진다.)
npm install passport-google-oauth20
로그인 버튼 만들기
버튼을 누르면 /auth/google로 get요청이 전송됨.
로컬로그인에서 로컬전략을 등록 한 것 처럼 구글로그인도 구글 전략을 로그인 요청이 들어오는 라우터에 등록해야한다.
app.get('/auth/google', passport.authenticate('google'));
로컬전략처럼 구글전략을 passport.js에 만듬.

구글전략 생성자의 첫번째 파라미터로, 처음에 나의 서비스를 구글에 등록시키고 받은 클라이언트 아이디와 클라이언트 시크릿 + 그것이 인증되면 엑세스토큰을 받을 콜백url + 스코프(구글에서 어떤 종류의 유저 정보를 받아 올 것인지)의 정보가 들어간 객체를 넣어줌.
여기까지 작성하고 구글로그인 버튼을 눌러보면
이렇게 구글이 내 앱이 인증된 앱이라는것을 확인하고 로그인 화면을 응답해줌. 로그인을 진행해보면 
이런 화면이 뜸. 주소창을 보면 /auth/google/callback?code=asfeafs($#s)afw@(#$라고 적혀있음.
처음 리디렉션 받을 주소를 입력했던 대로 구글이 사용자의 동의를 확인하고 엑세스코드를 응답해준것임.
그러면 /auth/google/callback으로 화면이 전환되면 그 때 서버는 엑세스코드와 함께 구글에 사용자정보를 요청하면 되는것임.
그렇게 처리 하기 위해 server.js에 라우터를 추가함. 바로 앞에서 정리한것 처럼 해당 주소로 요청이 오면, 다시 한번 구글전략을 실행시켜 엑세스코드와 함께 사용자의 정보를 요청함. (이것은 내가 따로 기입해주지 않아도 passport가 넣어줌. 이게바로 모듈이 필요한 이유군!)
로그인에 성공하면 /페이지로 이동하게되고, 실패하면 /login페이지로 이동함.
등록 후 다시한번 구글로그인을 해보면
유저 정보를 잘 받아와서 입력한대로 콘솔에 잘 찍히는것을 확인 할 수 있음.
그러면 이제 유저정보를 이용해서 로컬로그인에서 했던 것 처럼 User모델을 생성하여 db에 저장하고, 세션토큰을 생성하고, 로그인을 완성하면 됨. 아주 똑같음!
해당 부분을 마저 작성해주면 됨.
User모델은 아까 통신에서 받아온 id를 db에서 탐색하고, existingUser에 값을 넣음. 값이 존재하면 유저를 다음 미들웨어로 넘기고, 없으면 새롭게 User 모델을 생성해서 db에 저장함.
이메일이 이렇게 생겨서 이메일[0]으로 담음.
다음 미들웨어로 넘어가면 로컬로그인에서 그랬던것처럼
이 코드를 타고 세션에 저장됨.
여기까지 해보면! 무한로딩에 걸리는데! 전에 로컬로그인에서 작성한 코드를 수정해야함.
이부분인데 원래 마지막 next()가 없었음. 이 스키마는 저장하기 전에 비밀번호를 암호화 할 때 사용되는데, 비번이 있고 그것을 수정할 때만 next()가 되어서 코드가 진행되고 그렇지 않은 경우에는 next()가 없어서 무한로딩 됐던것. 추가하면 잘된다.
이제 로그인 해보면

성공해서 인덱스페이지로 이동되고, db에도 잘 저장되어있다. 이후에 구글로그인버튼을 누르면 이미 값이 있기 때문에 자동으로 로그인된다.
은 과감하게 생략한다! 시간이 없다! 강의에서도 비중있게 다루지 않고 코드만 작성하고 넘어갔다! 나에겐 선택과 집중이 필요하다!
짧게만 요약하면 nodemailer라는 것을 사용하면 편하다! nodemailer의 원리는 SMTP이다! SMTP는 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다! 메시지 전달을 위한 주요 전송 수단이다! 서로 다른 이메일 호스트(구글이랑 야후랑 이메일 보내고 받기) 간에 사용되는 프로토콜이다! 누군가 이메일을 작성해서 보내면 그 메일은 SMTP서버로 전송이 된다! (구글이메일이면 구글SMTP!) 상대방의 메일서버로도 SMTP로 전송된다. 상대가 그것을 가져갈때는 SMTP가 아니라 POP, IMAP, HTTP중 하나를 사용한다!
필요하면 노션에 공부하면서 메모한걸 보자. 보람아!
Kakao OAuth Key 발급받기
로그인
내 애플리케이션 → 추가하기 → 저장 → 생성된 애플리케이션 선택

화면 왼쪽 리스트에서 플랫폼 → web플랫폼 등록 (현재 사용중인 서버의 주소)


화면 왼쪽 리스트에서 카카오 로그인 → 활성화
사용자 권한 부여 후 엑세스 코드를 받을 리다이렉트 uri 입력

화면 왼쪽 리스트 → 동의항목 설정 ( 이메일은 비지니스 등록을 하거나 개인 사업자의 경우 개인 개발자 비즈 앱 전환에 동의해야 사용 가능 )

사용자 권한 요청시 사용할 RESTAPI key (우리는 웹으로 통신하니까) 저장해놓기.
구현
구글과 거의 유사하다!
npm install passport-kakao
버튼생성 ( 구글 때 해둠 )
라우터 생성 
카카오 전략 생성
구글전략 부분이랑 살짝 다른게 user.save()가 then을 사용해서 처리된다. 이게 어쩔때는 user.save()에 콜백함수를 넣지말라고하고, 어쩔때는 그냥 실행이 돼서 수정을 해야할지 말아야할지 잘 모르겠는데.. 일단은 지금은 카카오전략은 수정을 해야 로그인이 되고, 구글전략은 수정하지 안하도 로그인이 된다.
db에 저장 될 수 있도록 userSchema 수정
카카오 로그인 버튼을 눌르면


로그인 화면도 잘뜨고, 로그인도 진행되며 db에도 저장된다.
소스 코드 안의 어떤 값들은 개발 환경이나 운영 환경에 따라서 다르게 코드를 넣어줘야 할 때도 있고, 남들에게 노출되지 않아야 하는 값도 있다.
이런 코드를 위해서 설정파일을 따로 만들어서 보관 할 수 있다.
사실 이부분을 배우기 전에 db주소 같은게 노출되면 안될 것 같아서 따로 파일을 만들어서 exports하고 그파일은 .gitignore에 추가해서 관리하고 있었다. 두개의 차이는.. 잘모르겠다 난 둘다 편했다. 왜냐면 어차피 env파일도 .gitignore에 추가되어서 노출되지 않는거다. 차이라고하면 env는 모듈을 사용해서 사용법이 획일화 되어있다는것? 협동에서는 더 좋긴 할 것 같다.
npm i config (맥기준, 윈도우는 추가로 설치해야함)
루트디렉토리에 config라는 폴더를 만든 후 그 안에 JSON이나 YAML형식의 파일을 생성함.

default파일은 공통적으로 모든 환경에서 필요한 것을 넣는다. 위 이미지에서 쿠키의 유효기간을 14일로 설정했다.
development는 개발환경에서 필요한 정보를 넣는다. 개발환경에서 파일이 실행되면 (npm run dev) 디폴트+디벨롭에서 값을 넣고 실행한다.
만약 위처럼 디폴트에서 포트는 4000이지만 디벨롭에서 3000인데 npm run dev하면 3000으로 실행된다.

production은 운영환경에서 필요한 정보를 넣는다. 운영환경에서 실행되면 (npm run start) 디폴트+프로덕션에서 값을 넣고 실행한다.
위와같이 작성하면 npm run dev시 무조건 2000번으로 실행한다.
npm i dotenv.env 파일 생성require('dotenv').config();
require('./config/passport');변수에 할당하지 않으면서 require를 사용하는 것은 그 모듈의 코드를 실행하고 그 결과물을 즉시 사용하는 것을 의미합니다.
.config() 함수는 dotenv 모듈에 속한 함수로, dotenv 모듈을 로드하고 해당 모듈을 초기화하여, .env 파일에서 로드한 환경 변수들을 현재 프로세스의 환경 변수에 할당합니다.
require('./config/passport');이 코드는 왜 있는건지 진짜 모르겠는데 ㅇㅅㅇ;;;; 이 코드가 없으면 Error: Failed to deserialize user out of session 하고 오류가 난다. 이게 역직렬화라는데.. 역직렬화는 직렬화된 파일 등을 역으로 직렬화하여 다시 객체의 형태로 만드는 것을 의미한다. 저렇게 패스포트를 처음에 실행해서 초기화하지않으면 먼가.. 오류가 생기는건가..? stackoverflow도 봤는데 잘 모르겠따 ㅇㅅㅇ;;
저장된 파일을 읽거나 전송된 스트림 데이터를 읽어 원래 객체의 형태로 복원한다.

변수명=값과 같은 형태로 저장하고
이런식으로 필요한곳에서 사용 할 수 있음.추가로 라우터도 정리해줬는데, 이건 mvc부분에서 한번 공부했으니 다시 적지는 않겠다!
https://github.com/KingBoRam/Oz_study/tree/main/challenge/node.js/7%EC%9D%BC%EC%B0%A8/server
강의에서 프론트는 리액트로 구현하여 제공해줬고, 서버기능을 구현해야했다.


크게 유저요청(로그인, 로그아웃, 정보수정) / 숙소예약 / 숙소등록으로 나뉘어진다.
엔트리파일은 index.js이고 package.json에 설치해야할 모듈이 다 적혀있음으로 npm install 하고 작업을 시작한다.

엔트리파일에 기본설정을 한다. express app을 생성하고, 유저 세션을 쿠키에 저장할 것이기 때문에 cookie-parser과 cookie-session을 등록한다.
쿠키세션의 옵션은 위와같다. 쿠키의 유지시간 단위는 ms라서 초단위로 계산하기위해 *1000을한다. 거기에 60을 곱하면 1분, 또 60을 곱하면 1시간 또 24를 곱하면 하루가된다.
sameSite 속성은 cross-orgin을 설정하는 옵션이다. none으로 설정되면 모든 사이트로부터의 요청에서 쿠키가 전송된다.
body에 담겨 오는 요청을 parsing하기 위해 express.json도 등록하고, cors 설정도 해준다. credential 옵션은 브라우저가 CORS 요청을 할 때 자격 증명(쿠키, HTTP 인증 등)을 함께 전송할지 여부를 결정한다.
그리고 서버를 시작한다.

엔트리 파일에 추가한다.
db에 각 collection으로 관리하기 위해 schema를 생성한다. places에는 숙소 정보를, booking은 예약관리, user는 user정보를 관리한다.
const { default: mongoose } = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
picture: {
type: String,
required: true,
//no user images
default:
'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png',
},
});
const User = mongoose.model('User', userSchema);
module.exports = User;
const { default: mongoose } = require('mongoose');
const placeSchema = new mongoose.Schema({
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
title: {
type: String,
required: true,
},
addredd: {
type: String,
required: true,
},
// 사진은 여러장이니까 배열
photos: [{ type: String }],
// 설명
description: {
type: String,
},
// 장소 옵션
perks: [{ type: String }],
extraInfo: {
type: String,
},
price: {
type: Number,
},
});
const Place = mongoose.model('Booking', placeSchema);
module.exports = Place;
const { default: mongoose } = require('mongoose');
const bookingSchema = new mongoose.Schema({
// 어떤 유저가 예약했는지
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
// 장소는 어딘지
place: {
type: mongoose.Schema.ObjectId,
ref: 'Place',
required: true,
},
// 언제 체크인 할지
checkIn: {
type: Data,
required: true,
},
// 언제 체크아웃 할지
checkOut: {
type: Data,
required: true,
},
// 예약자 이름
name: {
type: String,
required: true,
},
// 예약자 연락처
phone: {
type: String,
required: true,
},
// 가격
price: {
type: Number,
required: true,
},
});
const Booking = mongoose.model('Booking', bookingSchema);
module.exports = Booking;
이전에 한것과 대체로 같은데, 눈여겨 볼점은 mongoose.Schema.ObjectId이다.
실제로 db를 보면 이렇게 objectId로 설정되어있다. 최상단에 있는 _id는 내가 기입해준것이 아니라 db의 컬렉션에 값이 하나 씩 추가될 때마다 mongoose가 붙여주는것이다. 그래서 컬렉션 간의 관계를 정의하고 관련 문서를 쿼리하거나 조인할 수 있다. 좀 더 쉽게말하면 서로 키가 있고 그 키를 가지고 컬렉션들끼리 서로 문서를 살펴보고 그걸 키로 작성해서 가지고 있을 수 있다는것이다.
나중에 해당 영역을 펼쳐서 값을 가져오도록 설정 할 수 있다. 아마 불필요하게 데이터가 중복되지 않도록 하기 위함인 것 같다.
엔트리 파일에 /경로로 요청이 들어오면 ./routes경로로 전달하도록
등록한다.
그리고 routes 폴더에 index.js 파일을 생성하여 라우터를 등록한다. /user경로로 요청이 오면 ./user경로로 요청이 전달되는 똑같은 방식이다.
다른 라우터들도 기본설정만 해주고 추후에 추가한다.
회원가입
코드 흐름은 다음과 같다. register버튼을 누르면 body에 name, email, password가 담겨 요청이 전송된다.
그리고 유저의 이메일이 db에 이미 있으면 에러메세지를 응답하고, 없으면 user를 새로 생성한다. 
생성할 때 비밀번호가 암호화 되도록 스키마에 추가한다.

이때 토큰을 생성하여 쿠키에 담아 응답해야한다. 쿠키생성은 userschema에 추가한다.
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRY,
});
};
전에는 그냥 jwt.sign을 index.js에 입력하는 방식이었다. 지금 사용한 방식과 차이점은 userSchema의 method로 getJwtToken을 추가한다. 해당 메소드 역시 jwt.sign을 통해서 토큰을 생성한다. 즉 앞으로 생성되는 User에 접근하면 해당 메소드를 사용 할 수 있다.
const cookieToken = (user, res) => {
const token = user.getJwtToken();
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_TIME * 24 * 60 * 60 * 1000,
),
httpOnly: true,
sameSite: 'none',
};
user.password = undefined;
res.status(200).cookie('token', token, options).json({
success: true,
token,
user,
});
};
module.exports = cookieToken;
이렇게 utils폴더에 cookieToken.js라는 파일을 생성하고, 그 안에서 만들어준다. cookieToken()은 생성된 user와 res를 받아서, getJwtToken()를 내부에서 사용하여 토큰을 생성하고, 쿠키에 담아서 응답한다. 이 때 응답되는 데이터에 user의 정보가 있는데 비밀번호가 노출되지 않도록 undefined로 값을 없애준다.
exports.register = async (req, res) => {
try {
const { name, email, password } = req.body;
if (!email || !password || !name) {
return res.status(400).json({
message: '이름, 이메일, 비밀번호를 모두 입력하세요.',
});
}
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
message: '이미 가입된 유저입니다.',
});
}
user = await User.create({
name,
email,
password,
});
cookieToken(user, res);
} catch (error) {
console.error('에러 발생:' + error);
res.status(500).json({
message: '서버 내부 오류가 발생했습니다.',
error: error,
});
}
};
그리고 아까 만들던 회원가입 기능에 user를 생성한 뒷부분에 cookieToken()을 사용해서 쿠키를 만들고 응답하도록 한다.
라우터에 등록한다. 이미지에 적힌대로 이전과 동일한 방식으로 작성하지 않는것에 의문이 들었는데,, 수정해보니 똑같이 동작한다. 아마 명시적으로 작성하기 위함인 것 같다.
여기까지 작성해보면 콘솔에 경고창이 뜨는데
db.js에 해당 코드를 추가한다.
로그인
흐름 : 버튼을 누르면 이메일과 비밀번호를 body에 담아 요청한다. db에서 해당 유저를 찾고, 존재하면 비밀번호를 비교하여 맞으면 쿠키토큰에 저장한다.
usercontroller에 생성한다. 아까 한것처럼 스키마에 메소드를 추가하여 비밀번호를 확인한다.
userSchema.methods.isValidatedPassword = async function (userSentPassword) {
return await bcrypt.compare(userSentPassword, this.password);
};
유저가 보낸 비밀번호르 받아서, db에 저장된 비밀번호와 일치하는지 확인한다.
값이 존재하여 if문을 통과하면 cookieToken을 사용하여 쿠키에 토큰을 저장한다.
이제 라우터에 등록한다.
cloudinary에 이미지 업로드

http://localhost:5173/account/places/new에 접근하면 이미지와같이 숙소를 예약 할 수 있다. 이때 숙소의 사진을 반드시 5장 이상 추가하도록 설정되어있는데, 이미지를 추가하기 위해서 cloudinary를 사용한다.
왜 이렇게 해야하는지 채고의개발자 염티쳐에게 물어봤는데, 자체적으로 url을 생성 할 수 있는 실제 서비스의 경우에는 이러한 작업이 필요 없지만, 학생 입장에서는 그렇지 않기 때문에 클라우드를 이용해야 한다고 했다.
클라우디너리를 사용하려면 우선 https://console.cloudinary.com/pm/c-fa13abb579d80a29380511164ca075/getting-started 접속해서 회원가입을 한다.

그리고 제공되는 credential을 저장해둔다.
엔트리 파일에 클라우디너리 설정을 해준다.
흐름 : /upload 경로로 post 요청이 오면, multer라는 모듈을 사용하여 이미지를 로컬폴더에 저장하는 미들웨어를 하나 거치고, 로컬폴더에 저장된 이미지들을 하나씩 클라우디너리에 저장되게 만든다. 그리고 클라우디너리에 저장되는 파일들을 하나씩 배열에 담아서, 응답에 포함시킨다.
const express = require('express');
const router = express.Router();
const cloudinary = require('cloudinary').v2;
const multer = require('multer');
const upload = multer({ dest: '/tmp' });
router.post('/upload', upload.array('photos', 100), async (req, res) => {
try {
} catch (error) {
});
Multer는 Node.js에서 파일 업로드를 쉽게 처리하기 위한 미들웨어입니다. Express.js와 함께 사용되며, 클라이언트에서 서버로 파일을 업로드할 때 요청을 처리하고 파일을 디스크에 저장하는 데 도움이 됩니다.일반적으로 Multer는 HTML 폼을 통해 파일을 업로드할 때 사용됩니다.
클라이언트가 서버로 POST 요청을 보낼 때, Multer는 해당 요청의 본문을 해석하여 파일 업로드를 처리하고, 서버 측에서 이러한 파일을 처리할 수 있도록 해줍니다.
간단하게 말해서 multer는 업로드된 파일을 서버에 저장할 때 사용하는 모듈이다. 왜 이 과정이 필요한지 모르겠어서 염티쳐에게 물어봤는데, 클라우디너리 서비스 자체가 이미지를 등록하려면 그냥 url을 입력하면 되는게 아니라 파일을 등록하도록 되어있기 때문이라고 한다.
/tmp라는것은 서버에 생성되는 임시 디렉토리라고한다. 즉 multer를 사용해서 이미지를 받으면 서버의 임시디렉토리에 저장되었다가 클라우디너리에 전송되고, 사용된 임시 파일들은 이후에 제거된다고 한다.
위 코드는 한번에 최대 100장의 이미지를 저장한다.
router.post('/upload', upload.array('photos', 100), async (req, res) => {
try {
let imageArray = [];
for (let i = 0; i < req.files.length; i++) {
let { path } = req.files[i];
const result = await cloudinary.uploader.upload(path, {
folder: 'Airbnb/Places',
});
imageArray.push(result.secure_url);
}
res.status(200).json(imageArray);
} catch (error) {
console.log(error);
res.status(500).json({
error,
message: 'Internal Server Error : 서버에 오류났어요.',
});
}
});
마저 코드를 작성한다. 우선 muler를 사용해서 tmp에 저장되고나면, 그 경로를 다음 핸들러가 받아서 path에 할당한다. 클라우디너리의 업로더가 업로드라는 메소드를 사용해서 그 경로로를 사용하여 'Airbnb/Places'에 이미지를 저장한다. 사용자에게 응답할 때 사용될 이미지의 주소는 secure_url로 담긴다.

실제로 사용해보면 이미지를 업로드하면 클라우디너리에 잘 등록된 걸 확인 할 수 있다.
프로필 편집
edit profile을 눌러서 이름, 사진, 비밀번호를 수정하는 핸들러를 만든다.
흐름 : 받아온 유저 데이터중 수정되지 않는 email을 이용해서 해당 유저의 정보를 가져오고, 새로 받은 값들로 유저데이터를 수정해준다.
수정이니까 요청은 put으로 온다.
이름은 처음부터 기입되어 있기 때문에 바로 담는다. 그러나 사진 혹은 비밀번호는 수정이 되었을 수도 있고, 아닐수도 있기 때문에 조건문을 붙여서 작성한다. 그리고 업데이트된 내용을 저장한다. 그런데 조건문이 좀 .. 뭐랄까.. // user can update only name, only password,only profile pic or all three 이렇게 밖에 안된다.. 암튼 저장하고나면 cookieToken이 새로 토큰을 만들어서 응답한다.
실제로 이미지 변경하기
사진은 그냥 유저가 등록한 주소만 바꾸면 되는게 아니라 클라우드에 올리고 그걸 유저에게 제공해야하니까, 추가로 작성해줘야함.
마찬가지로 멀터를 거쳐서 경로를 받음.
r궁금해서 직접 찍어봤음. 받은 경로가 멀터경로더라.
req.file은 웹 애플리케이션에서 클라이언트가 서버로 업로드한 파일에 대한 정보를 포함하는 객체로 /tmp에 있는 파일을 하나씩 업로드할것.
이때 라우터를 잘 보면 아까랑 다르게 한장만 저장할거라 .single()을 사용하고 있음.
res.cookie(키, 값, 옵션) 이니까 쿠키에 token의 값을 null로 바꾸고 유효기간을 현재시간으로 바꿔버리면 토큰이 아예 종료가됨숙소 등록
숙소 등록 페이지에서 나머지 부분을 기입하고 db에 숙소들이 하나씩 저장되도록 해보자.
흐름 : body에 담겨온 정보를 schema.create해서 db에 새로운 값을 생성하고, 클라이언트에게 응답한다. 이 과정이 되려면 유저가 로그인 되어 있어야 함으로, 로그인 인증 함수를 한번 거치도록 한다. 로그인인증함수는 요청의 쿠키를 가져와서 복호화하여 인증한다.

req.user에 담으면 다음 미들웨어에서 접근할 수 있다
잘 저장됨
화면에 처음 접속하면 place에 대한 모든 데이터를 가져와서 화면에 띄우기

Place.find() 하면 디비의 Place에 있는 모든값을 가져옴.
유저가 등록한 장소만 보여주기
id를 받아와서 owner의 id와 일치하는 모든 데이터를 받아와서 응답함. 이 핸들러는 아까 만든 isLoggedIn미들웨어를 거친 후에 실행될것임. 그래서 아까 말한것처럼 req.user에 user의 정보가 존재하기 때문에 id를 사용 할 수 있음.
라우터에 등록함.
선택한 숙소의 상세정보 보기

get요청의 params로 해당 숙소의 id를 받아서, 그 값만 찾아서 응답함.
검색으로 원하는 숙소 찾기
exports.searchPlaces = async (req, res) => {
try {
const searchWord = req.params.key;
if (searchWord === '') return res.status(200).json(await Place.find());
const searchMatches = await Place.find({
address: { $regex: searchWord, $options: 'i' },
});
res.status(200).json(searchMatches);
} catch (error) {
console.log(error);
res.status(500).json({
message: '서버오류에용',
});
}
};
주소에 검색에 사용된 key를 params로 받아와서 Place db에서 해당 내용을 찾아야함.
정규 표현식(Regular Expression, regex)은 문자열을 검색하거나 조작하는 데 사용되는 특수한 문자열 패턴입니다. 특정한 규칙을 따르는 문자열 집합을 정의할 수 있으며, 이를 사용하여 문자열에서 원하는 패턴을 찾거나 변경할 수 있습니다.
예를 들어,{address : {$regex : "searchWord"}}에서$regex는 MongoDB 쿼리 언어에서 사용되며, 주어진 패턴("searchWord"라는 단어)을 포함하는 문자열을 찾습니다. 여기서"searchWord"대신에 정규 표현식 패턴을 사용할 수도 있습니다.
즉 값으로 받은 단어가 포함되어 있는걸 찾아오는거고, 옵션 i는 소문자 대문자 신경없이 찾아준다는 옵션이다.
라우터에 등록하고 실행해보면
34가 포함된 숙소만 잘 보여준다.

위 이미지처럼 원하는 숙소에 들어가 예약하는 기능을 만듬.
흐름 : req.body에 담겨오는 예약날짜, 이름, 가격, 폰번호 등을 Booking model을 사용하여 db에 저장하고 응답한다.
예약이 끝났으면 내가 예약한 숙소를 볼 수 있는 핸들러도 만들어준다.
여기서 눈여겨 볼점은 .populate('place')이다.
앞에서 스키마를 만들 때
이런식으로 mongoose.Schema.ObjectId로 값을 저장했다. 그래서 db에도
이렇게 저장되어있는데, 값을 받아올때도 풀어서 오는게 아니라 저상태로 받아와지기때문에, 실제로 해당 아이디의 정보를 담아서 받아오라는 것
콘솔에 찍어보면 풀어주지 않은 owner는 그대로 id값만 들어있지만, place는 해당하는 db에서 값을 잘 받아와서 넣어줬다.
계속 사용되는 find가 각각 무슨 차이가 있는지 알아봄
1..find: 이 메소드는 주어진 조건에 맞는 모든 문서를 반환합니다. 예를 들어,db.collection.find({})와 같이 사용하면 해당 컬렉션의 모든 문서를 반환합니다.
2..findOne: 이 메소드는 주어진 조건에 맞는 첫 번째 문서를 반환합니다..find와 달리 단 하나의 문서만 반환하며, 그것도 조건에 맞는 첫 번째 문서입니다.
3..findById: 이 메소드는 MongoDB의 문서를 ID로 검색할 때 주로 사용됩니다. 이 메소드는 주어진 ID에 해당하는 단일 문서를 반환합니다. 주로 MongoDB의 문서들은 고유한 ID를 가지고 있으므로, 특정 문서를 검색할 때 편리하게 사용됩니다.
4.24 node.js가 무엇인지 대략적으로는 알고있었지만 실제로 이용해보니 생각보다 재밌었다. 사실 수업을 시작하기 전에는 이게 서버를 만드는데 주로 사용된다는데.. 조금 수업을 널널하게 들어볼까?! (안한다는건 아니다..)라고 생각했었는데 막 내 컴퓨터로 서버를 간단하게 열어보고 실습하니까 재밌었다! 아직은 쉽지만 곧 막 서버어쩌구시작하면 어렵겠지?
4.28 처음 접하는 node.js 서버 부분에 들어가니까 역시나 바로 너무 이해가 안되서 ㅋㅋㅋㅋ 해당 부분을 시작한게 4.26이었는데 이해와 정리를 하는데 무려 이틀이나 걸렸다! 진짜 힘들었는데.. template engine을 배우는 부분에서 이게 바로 내가 node.js를 배워야 하는 이유구나.. 하는 어떤 깨달음을 얻었다. SEO에 좋지 않아서 REACT대신 사용된다는 NEXT.js가 SSR이라는 개념을 들었을때는 그냥 아 서버에서.. 랜더링 하는구나.. 하고 넘어갔는데 이런식으로 동작하고 또 자바스크립트를 통해서 이렇게 만드는구나 하고 이해하니까 잠깐 사용해봤던 NEXT.js에 퍼블릭 폴더는 왜 있었는지, 왜 그렇게 작성해야 했는지를 어느정도 이해하게 됐다. 정말 세상에 필요없는 공부는 없다.
4.29 이게 참.. 새로운걸 배우면 공부 하는 중에는 내가 다 알고 있는 것 같지만 혼자서 실습해보려면 내가 얼마나 1도 이해 못했는지 느껴지는 것 같다 ㅋㅋㅋ... 완전 처음 해보는 서버 부분을 실습하니까 더 그렇다.. 처음엔 서버공부를 왜 하지? 생각했는데 슬슬 알것도 같다. 내가 서버가 이렇게 동작한다는걸 몰랐다면 클라이언트측에서 코드를 짤 때 동작원리는 하나도 이해하지 못하고 그냥 이렇게 해야한대~ 하고 작성했을 것 같다. 그리고 그러한 방식은 아마 오류가 생기면 문제해결이 아주 힘들어 질 것 같다. 학습은 힘들지만.. 오늘도 내가 열심히 해야 하는 이유를 찾아냈다. 힘내자~!
4.30 힘..힘내..? 너미친거야..? 힘을 낼 수가 없어.. 진짜 오늘은 코드도 어렵고 개념도 어려웠지만 5시간동안 오류를 못해결한게 정말 크리티컬했다.. 사실 처음에 app.use(passport.session());를 주석처리 하니까 정상적으로 동작하길래 아싸 개꿀 하고 넘어갔는데 당연히 세션저장이 안되니까 그 후 코드가 자꾸 오류가 났다ㅜㅜ 결국 처음으로 돌아가서 이게 왜 오류가 나는지를 파악해야했고..
그러다보니까 커밋 되돌리려고 revert도 쓰는데 익숙하지 않아서 의도대로 동작하지도 않고.. 하다보니 5시간이 흘렀다 흑흑 .. 안그래도 오늘 부분은 지금 수준에서는 어려울 수 있으니 오늘은 흐름만 파악하고 넘어가라는 말을 들은 참이었어서 정말 오늘은 다 때려칠뻔했다.. 끝까지 포기하지 않은 나에게 증말 칭찬을 해주고 싶다.. 현재 시간 새벽 4:14인데.. 그래도 내일이 연휴여서 이시간까지 정리하고 넘어 갈 수 있어서 정말 다행이다.
5.2 20분자리 영상을.. 이해 안되는 부분이 너무많아서 1시간 30분동안 봐야했다. 뭔가 자바스크립트를 다룰때는 표면적이고 일차적으로 코드를 다루다가 갑자기 서버쪽 node.js에 넘어오니까 입체적이게 여기갔닥 ㅏ저기갔다가 이함수가 저함수랑 연관되고 이함수가 저함수를 부르고 이렇ㄱ ㅔ복잡해지니까 정신을 못차리겠다. 바닐라 자바스크립트를 할 때 좀 더 이렇게 복잡한 로직들을 접하고 넘어왔으면 좋았을텐데 하는 아쉬움이 생긴다.
5.3 nodejs..진짜 힘들었다..솔직히 말하면 너무 많은 내용을 빠르게 배워야해서 강의에서 생략된 부분이 너무 많았다. 그래서 길이는 10~20분이면 나는 그 3배는 검색을하고 따로 공부하는 시간을 가져야했다.. 심지어 너무 시간이 촉박해서 이메일 전송하는건 중간에 최신 코드로 수정까지 했는데도 도저히 블로그에 작성할 엄두가 나지 않았고..마지막으로 한 에어비엔비는 5.3일에 봐야하는 스케줄이었지만.. 작성하는 지금은 5.5로 중간에 오류도 생기고 모르는것도 계속 생기니 자세히 보는데 2일이나 걸렸다.. 주말이 끼어있어서 다행이었지만.. 그랬는데도 내가 이걸 완전히 이해했다는 생각은 전혀 들지 않는다 ㅠㅠ.. 조교님들은 이걸 완전히 다 할줄알면 그건 풀스택 개발자고 흐름만 이해하라고 하셨지만.. 이렇게 강의를 들어서는 흐름도 이해가 안되는데 어떻게 하겠는가 ㅠㅠ.. 어쨋든 스스로 어느정도 이해할만큼은 노력했고.. 나의 최선을 했다..휴ㅜ
나의 깃헙 커밋 ㅋㅋㅋㅋㅋㅋ 힘들구나..