[Node.js] fetch를 이용해 프런트의 정보를 서버로 보내기

·로그인 페이지에서 입력한 아이디와 비밀번호 받아오기 1) login.js파일에서 로그인 버튼을 눌렀을 때 동작하는 함수에 fetch 기능 추가하기 // login.js function login() { const req = { id: id.value, pw: pw.value,

data-science-study.tistory.com

·이전 글에서 home.ctrl.js 파일에서 login 프로세스가 있을 때 단순히 console.log에 띄우는 작업만 하던 것을 id와 pw를 비교하는 과정으로 변경

 

1) 임의의 id와 pw리스트 지정하고 프런트에서 받은 id, pw와 비교

// home.ctrl.js

// 원래는 controller에 id와 pw 리스트가 있으면 절대 안되지만 연습용으로 controller에 작성
const users = { // 각 사용자의 id와 pw를 리스트로 만들어서 같은 번지에 부여
    id: ["사용자1", "사용자2", "사용자3"],
    pw: ["1234", "3456", "5678"]
};

// login process 작성
const process = {
  login: (req, res) => {
    const id = req.body.id,                         // login.js에서 fetch 안에서 body로 받은 id와 pw를 각각 id와 pw 변수로 받기
           pw = req. body.pw
    if (users.id.includes(id)) {                     // 만약 위에서 임의로 작성한 id 리스트에 프런트에서 받은 id가 존재한다면
      const idx = users.id.indexOf(id);         // 그 id의 인덱스를 idx 변수로 받기
      if (users.pw[idx] === pw){               // 그리고 pw 리스트의 같은 인덱스에 있는 pw와 프런트에서 받은 pw 비교하여 같으면
        return res.json({                            // json 형태로 success: true 전달
          success: true,
        });
      }
    }
    return res.json({                               // 만약 위에서 실패하여 success: true를 return하지 못했다면
      success: false,                                // success: false와 msg: "로그인에 실패하셨습니다."를 json 형식으로 return
      msg: "로그인에 실패하셨습니다.",
    });
  },
};

 

2) login.js에서 success가 true인지 false인지에 따라 응답할 행동 지정

// login.js

function login() {
    const req = {
        id: id.value,
        pw: pw.value,
    };

    fetch("/login", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(req)
    })
    
//////////////////// 여기까지는 동일 ///////////////////////
 
        .then((res) => res.json())
        .then((res) => {
            if (res.success) {                  // res.success가 true이면
                location.href = "/"            // "/" 경로로 이동
            } else{
                alert(res.msg);                // res.success가 false이면 res에 있는 msg를 경고창으로 띄움
            }
        })
        .catch((err) => {                    // 로그인 중 에러 발생 시 console에 에러 표시
            console.error(new Error("로그인 중 에러 발생"));
        });
}

 

3) 확인

  -로그인 실패 시

 -로그인 실패 경고창

 

 

 -로그인 성공 시

 -코드에서 지정한대로 루트 경로로 이동

·로그인 페이지에서 입력한 아이디와 비밀번호 받아오기

1) login.js파일에서 로그인 버튼을 눌렀을 때 동작하는 함수에 fetch 기능 추가하기

// login.js

function login() {
  const req = {
    id: id.value,
    pw: pw.value,
  };
  
  // login 경로에서 / http 메소드 중 POST 메소드를 사용하여 / 전달하는 데이터가 json형식임을 명시
  fetch("/login", {
    method: "POST"
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(req) // JSON 객체는 문자열로 감싸져서 출력됨
                                         // 그냥 req는 {id: "id", pw: "pw"} 형식
                                         // JSON.stringify(req)는 {"id": "id", "pw":"pw"}
  });
}

 

2) home.ctrl.js파일 원래는 경로에 따라 화면만 렌더링해주는 명령어만 있었지만 login에 대한 동작도 추가

// home.ctrl.js

"use strict";

// 화면 렌더링(지정된 경로의 주소로 접속했을 때 보이는 화면)
const output = {
    hello: (req, res) => {
        res.render('home/index');
    },
    login: (req,res) => {
        res.render('home/login');
    }
}

// 로그인 프로세스
const process = {
    login: (req, res) => {
        console.log(req.body) // 일단은 제대로 받아와지는지 확인을 위해 login.js 파일에서 body로 준 부분(id와 pw 값) console에 출력
    }
}

// 위의 모듈들을 index.js 파일에서 사용할 수 있도록 오브젝트로 빼주기
module.exports = {
    output,
    process,
}

 

3) index.js파일에서도 login 프로세스를 post를 사용할 수 있도록 코드 추가 및 home.ctrl.js에서 output으로 묶은 hello, login 모듈 에 대한 코드 변경

// index.js

"use strict"

const express = require('express');
const router = express.Router();

// home.ctrl.js에서 넘겨준 오브젝트 받기
const ctrl = require("./home.ctrl");

///////////////// 여기까지는 그대로 ////////////////////////////////

router.get('/', ctrl.output.hello); // output.hello로 변경하여 output안의 hello 모듈 사용할 수 있도록 변경
router.get('/login', ctrl.output.login); // output.login으로 변경하여 output안의 login 모듈 사용할 수 있도록 변경
router.post('/login', ctrl.process.login); // get이 아닌 post로 동작

module.exports = router; // 외부 파일(메인 파일인 app.js)에서 사용할 수 있도록 내보내기

 

4) app.js 파일에서 fetchdml body를 사용할 수 있도록 bodyParser를 등록

// app.js

const bodyParser = require("body-parser"); // home.ctrl.js 파일에서 login.js의 fetch함수에서 body로 넘겨준 것을 잘 받기 위한 모듈 설치

app.use(bodyParser.json()); // bodyParser 미들웨어 등록
app.use(bodyParser.urlencoded({extended: true})); // url을 통해 전달되는 한글, 공백 등의 문자가 포함될 경우 제대로 인식되지 않는 문제 해결

 -터미널에서 "npm install body-parser -s" 명령어로 body-parser 모듈 설치

 

5) 서버 가동 후 동작 확인

  -id와 pw를 입력하고 로그인 버튼을 누르면 터미널에 다음과 같이 id와 pw 값이 출력됨을 알 수 있음

  -id와 pw값 서버로 받기 성공

 

1. 자바스크립트의 데이터 타입

 1) 기본형: 숫자 / 문자열 / 불리언 / null / undefined / Symbol 등

 2) 참조형: 객체(Map / WaekMap / set / WeakSet) / 배열 / 함수 / 날짜 / 정규표현식

 

 

2. 데이터 타입 관련 배경지식

 1) 비트: 컴퓨터는 모든 데이터를 0 또는 1로 바꿔 기억하며, 0 또는 1을 표현하는 하나의 메모리 조각

  -각 비트는 고유한 식별자를 가짐

 

 2) 바이트: 1바이트는 8비트로 0 또는 1 하나로만 된 비트 단위로 위치를 확인하는 비효율성을 줄이기 위해 8개의 비트를 묶어 놓은 것

  -각 바이트는 시작하는 비트의 식별자로 위치를 파악

  -모든 데이터는 바이트 단위의 식별자(메모리 주솟값)을 통해 서로 구분하고 연결

 

 3) 자바스크립트는 숫자의 경우 정수형인지 부동소수형인지 구분하지 않고 64비트(8바이트)를 확보하는 등 형 변환해야 하는 상황 감소

 

 4) 식별자: 어떤 데이터를 식별하는 데 사용하는 이름(변수명)

 

 5) 변수: 변할 수 있는 무언가

 

 

3. 변수 선언

// 방법 1
var a;
a = 'abc';

// 방법 2
var a = 'abc';

 1) 선언: 메모리에서 비어있는 공간을 확보, 그 공간의 이름을 설정

  -메모리의 빈 공간(@1003)을 확보하고, 식별자(변수명)를 a로 지정

 

 2) 할당: 식별자를 통해 공간을 검색하여 해당 공간에 데이터를 할당

  -데이터를 저장하기 위한 별도의 메모리 공간(@5004)을 다시 확보하여 문자열 'abc' 저장

  -문자열 'abc'가 저장된 메모리 공간의 주소(@5004)를 식별자를 a로 지정한 메모리 공간(@1003)에 대입

테이블 만들기

변수 영역 주소 ... 1002 1003 1004 ...
데이터     이름: a
값: @5004
   
데이터 영역 주소 ... 5002 5003 5004 ...
데이터       'abc'  

 

 

4. 기본형 데이터와 참조형 데이터

 1) 불변값: 데이터 영역의 메모리 변경 가능성이 없는 값

  -기본형인 숫자, 문자, boolean, null, undefined, Symbol은 모두 불변값

  -변수와 상수의 구분도 변경 가능성이지만 이때의 변경 가능성은 변수 영역의 변경 가능성을 의미

// 1
var a = 'abc';
a = a + 'def';

// 2
var b = 5;
var c = 5;
b = 7

  -1번 예시: 변수 영역에서 식별자가 a인 공간에 'abc'라는 문자의 주소를 데이터 영역에서 찾아 주소를 할당

                  →'def'를 추가하여 'abcdef'라는 문자를 새로 할당하면 데이터 영역의 'abc'가 'abcdef로 바뀌는 것이

                      아닌 데이터 영역에 'abcdef'가 새롭게 추가되고 추가된 'abcdef'의 주소가 변수 영역의 a의 주소에 새로 할당

 

  -2번 예시: 변수 영역에서 식별자가 b인 공간에 5라는 숫자의 주소를 데이터 영역에서 찾아 주소를 할당

                  →변수 영역에서 식별자가 c인 공간에 5라는 숫자의 주소를 데이터 영역에서 찾아 주소를 할당(b에 할당된 5와 동일한 5)

                  →변수 영역에서 식별자가 b인 공간에 할당되어 있는 5를 7로 바꾸는 것이 아닌 데이터 영역에서 7이 할당된 주소를 찾아 7의 주소를 다시 할당

 

  -결론: 'abc', 'def', 'abcdef', 5, 7은 바뀔 수 없는 '불변값'

           / 선언된 a, b, c는 할당되는 값을 계속 바꿀 수 있는 '변수'

 

 2) 가변값

  -참조형 데이터 obj1에 값을 할당

var obj1 = {
  a: 1,
  b: 'bbb'
};

  (1) 변수 영역의 빈공간 @1003에 obj1 할당

  (2) 할당하려는 값을 데이터 영역에 저장하려는데 값이 여러 개로 이루어져 있으므로 그룹 별도의 변수 영역 생성(@7103~)하여 저장

      ※ 참조형 데이터에 계속 데이터가 추가될 수 있으므로 메모리 영역 크기는 동적으로 변할 수 있도록 할당

  (3) @7103과 @7104에 a와 b라는 이름의 식별자 지정

  (4) 다시 데이터 영역에서 a와 b에 할당할 값 1과 'bbb'를 찾거나 새로 할당(@5003, @5004), 각각의 데이터 주소를 @7103, @7104에 할당

     ※ @5003은 @7103에, @5004는 @7104에 할당

 

  - 변수 영역

주소 ... 1002 1003 1004 ...
데이터     이름: a
값: @5004
   

 

  - 데이터 영역

주소 5001 5002 5003 5004 ...
데이터 @7103 ~ ?   1 'bbb;  

 

  - 객체 @5001의 변수 영역(기본형 데이터와 참조형 데이터의 차이점 → 객체의 변수 영역이 별도로 존재)

주소 7103 7104 7105 7106 ...
데이터 이름: a
값: @5003
  이름: b
값: @5004
   

 

  -만약 참조형 데이터 obj1의 변수 중 a의 값을 2로 변경한다면

var obj1 = {
  a: 1,
  b: 'bbb'
};

obj1.a = 2;

  -'변수 영역'에는 변화 없음

  -'데이터 영역'에 새로 할당될 값 2가 없다면 빈 공간에 2를 추가

  -'객체 @5001의 변수 영역'에서 식별자가 a인 @7103의 값이 데이터 영역에서 2가 있는 주소로 변경됨

 

 3) 기본형 데이터와 참조형 데이터의 복사

  -복사 과정은 동일

// 기본형 데이터
var a = 10;
var b = a;

// 참조형 데이터
var obj1 = {c:10, d: 'ddd'};
var obj2 = obj1;

  →변수 영역에서 식별자가 b인 주소에 데이터 영역에서 10의 주소를 할당

  →변수영역에서 식별자가 obj2인 주소에 데이터 영역에서 obj1의 객체의 주소를 가진 주소를 할당

 

  -복사 이후에 값을 변경할 때 차이

b = 15;
obj2.c = 20;

  →기본형 데이터 b를 변경할 때는 데이터 영역에 15가 새로 할당되고 변수 영역의 식별자가 b인 영역의 값 주소를 15의 주소로 변경

       (기본형은 '데이터 영역'의 주소를 '변수 영역'에 복사)

  →참조형 데이터 obj2의 변수 중 c를 변경할 때는 처음에 obj1의 변수 영역에 할당되었던 c의 값 주소가 데이터 영역에서 20의 주소로 변경

       (참조형은 '데이터 영역'의 주소를 'obj1의 변수 영역'에 복사→ 'obj1의 변수 영역'의 주소가 담긴 '데이터 영역'의 주소를 '변수 영역'에복사)

  →결론: 기본형 데이터는 복사 후 한 쪽의 값을 바꿔도 다른 쪽은 바뀌지 않음

                    참조형 데이터는 복사 후 한 쪽의 값을 바꾸면 다른 쪽도 바뀜

 

  -다른 경우(참조형 데이터에서 객체 자체를 바꾸는 경우): 데이터 영역의 새로운 공간에 객체가 저장되고 그 주소가 obj2에 위치하므로

                                                                                                                      위에서 기본형 데이터 변경과 같이 복사한 개체와 남남이 됨

obj2 = {c: 20, d: 'ddd'};

  →참조형 데이터가 '가변값'이라는 것은 그 내부의 변수를 변경할 때만 성립, 객체 자체를 변경할 때는 불변값(기존 데이터는 변하지 않음)

1) document.querySelector()

 - 괄호 안에 객체의 id, name, 형식 등의 정보를 넣어 HTML 객체를 JS파일 내에서 정의

<!-- login.ejs -->

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인</title>
        <style>
            ul {list-style:none;}
        </style>
        <script src="/js/home/login.js"></script> <!--app.js에서 use 미들웨어를 통해 public 폴더 내의 js/home/login.js로 접근할 수 있게 됨-->
    </head>
    <body>
        <form id="login">
            <ul>
            <li>
                <label for="id">아이디</label>
                <input type="text" id="id">
                <!-- id가 "id"인 객체 -->
            </li>
            <li>
                <label for="pw">비밀번호</label>
                <input type="password" id="pw">
                <!-- id가 "pw"인 객체 -->
            </li>
            <input type="submit" value="로그인" id="loginBtn">
            <!-- id가 "loginBtn"인 객체 -->
            <button>회원가입</button>
            </ul>
        </form>
    </body>
</html>
// login.js

"use strict";

const id = document.querySelector("#id"),
      pw = document.querySelector("#pw"),
loginBtn = document.querySelector("#loginBtn"); // 각각 HTML에서 id가 "id", "pw", "loginBtn"인 객체를 받아옴

 

2) HTML문서에서 JS파일을 연결할 때, id가 정의되기 전 JS 파일이 id를 받아들여서 JS파일에서 객체가 null로 정의되는 것을 방지

 -script에서 JS파일 연결할 때 defer 명령어 추가

<script src="/js/home/login.js" defer></script>
<!-- defer를 사용하여 login.js의 실행 순서를 조정(id객체를 받기 전에 js파일이 실행되는 것 방지) -->

 

3) JS파일에서 HTML의 객체를 받은 것을 이용해 기능 구현

 -로그인 버튼을 클릭했을 때 id에 입력된 값과 pw에 입력된 값 console에 출력하기

// login.js

loginBtn.addEventListener("click", login);

function login() {
    const req = {
        id: id.value,
        pw: pw.value,
    };
    console.log(req);
}

 

4) 결과

·nodemon 모듈을 이용하면 파일을 변경할 때마다 서버를 끄고 키는 과정 없이 자동으로 갱신해줌

 

1) nodemon 설치: 터미널에 npm install nodemon -g

 -"-g" 명령어로 전역 설치 하기

 

2) "npm 파일경로" 대신 "nodemon 파일경로"명령어로 서버 가동

 -서버를 한 번 가동한 뒤에는 파일 내용 변경후 저장하기만 하면 자동을 ㅗ서버가 껏다켜지며 해당 내용이 적용됨

 

3) package.json파일에서 npm start 명령어에 저장해둔 명령을 "npm 파일경로"에서 "nodemon 파일경로"로 바꾸면 npm start 명령어를 사용해 nodemon으로 서버를 여는 기능 구현 가능

·"localhost:3000/login"으로 서버에 접근했을 때 보여지는 화면인 login.ejs파일과 화면의 동작을 담당하는 JS파일 연결

1) src > public > js > home 폴더를 생성한 후 login.js라는 이름으로 자바스크립트 파일 만들기

 

2) 자바스크립트 파일에는 간단하게 아래와 같은 코드 작성

// login.js
"use strict";

console.log("Hello");
console.log("bye");

 

3) login.ejs파일에는 자바스크립트를 연결시킬 코드 작성

// login.ejs
// 아무곳에나 코드 삽입하면 됨
<script src="/js/home/login.js"></script>

 

4) app.js에도 js폴더로 접근할 수 있도록 해주는 미들웨어(app.use) 생성

// app.js

app.use(express.static(`${__dirname}/src/public`))
// __dirname은 현재 파일이 있는 디렉토리 이름
// 해당 경로(app/src/public 폴더)를 정적 경로로 추가해주겠다는 의미

 

5) 서버 가동 후 localhost:3000/login에 접속하면 다음과 같이 자바스크립트 파일의 동작이 실행됨

·서버를 띄워주는 app.listen을 모듈화

1) 새로 bin 폴더를 만들어 폴더 안에 www.js라는 파일 생성

2) www.js 파일에 app.listen 부분 붙여넣기

// www.js
"use strict";

const app = require("../app.js"); // bin의 상위폴더('../')에서 app.js에 접근

app.listen(3000, () => {
  console.log('서버 가동');
});

 

3) app.js에서는 app.listen부분은 지우고 대신 www.js로 app객체를 넘겨줄 수 있도록 코드 작성(마지막 줄 수정)

// app.js
"use stirct"

// 모듈
const express = require('express'); // require를 사용하여 express라는 모듈 다운
const app = express(); // app이라는 변수에 express를 실행시켜서 넣어주기
const PORT = 3000;

// 라우팅
const home = require('./routes/home'); // 괄호 안의 경로에 있는 파일(index.js)을 읽어달라는 요청


// View 분리
app.set('views', './views'); // 화면 뷰를 저장하고 관리해줄 파일을 ./views로 지정
app.set('view engine', 'ejs'); // views 폴더 안에 생성될 html코드들을 어떤 엔진으로 해석할지 결정('ejs', 그냥 html이랑 비슷)

app.use('/', home); // use는 미들웨어를 등록해주는 메소드, index.js의 라우터를 받아옴

// 모듈화를 위해 내보낸 app.listen 함수가 www.js 파일에서도 app이라는 객체를 인식할 수 있도록 내보내기
module.exports = app;

 - app.listen에 포트 번호를 PORT객체를 통해 줬다면 www.js로 넘겨줬을 때 PORT를 숫자로 직접 바꿔야 함

 

3) 서버가동은 www.js파일을 통해 시켜야하므로 터미널에 'node ./bin/www.js'를 입력하여 실행

  -실행 명령어 단축키로 하는 방법

   (1) package.json파일 에서 "scripts":{} 안에 지정

// 원래 test만 있던 scripts에
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},
  
// 명령어 단축키 추가(start만 치면 node .bin/www.js 명령어가 실행됨
"scripts": {
  "start": "node ./bin/www.js",
  "test": "echo \"Error: no test specified\" && exit 1"
},

   (2) 터미널에 'npm start' 명령어 입력시 서버 가동됨

·분리한 라우팅에서 컨트롤러를 다시 분리

 -라우터는 해당 경로에 들어왔을 때 사용자의 요청을 연결해주는 부분

 -실제로 요청에 해당하는 기능을 수행하는 부분이 Controller(res.render() 부분)

 -Controller는 MVC 설계 패턴 중 C에 해당

 

1) 라우팅을 분리해놓은 폴더 안에 컨트롤을 분리할 파일을 새로 생성

2) 새로 만든 파일에 원래 라우터에서 기능을 수행하던 함수를 잘라내어 붙여넣고, 라우터에서 함수부분에는 임의의 함수 이름을 대신 작성

// index.js
"use strict";

const express = require("express");
const router = express.Router();

// home.ctrl.js에서 넘겨준 오브젝트 받기
const ctrl = require("./home.ctrl");

router.get('/', ctrl.hello); // hello는 "/" 경로로 접속했을 때 수행할 기능의 함수 이름
router.get('/login', ctrl.login); // login는 "/login" 경로로 접속했을 때 수행할 기능의 함수 이름

module.exports = router;
// home.ctrl.js
"use strict";

const hello = (req, res) => {
  res.render("home/index");
});

const login = (req,res) => {
    res.render('home/login');
};

// index.js에서 사용할 수 있도록 오브젝트로 빼주기
// 오브젝트는 원래 key와 value로 되어있는 구조인데 아래와 같이 key 하나만 입력해주면 자체적으로 key와 같은 value를 넣어둠
module.exports = {
  hello, // hello:hello, 과 같음
  login, // login:login, 과 같음
}

 

+ Recent posts