[SeSAC x 코딩온] 웹 풀스택 회고 14 | MVC 패턴 실습

HyeKong·2023년 8월 27일
0
post-thumbnail

MVC 패턴이란

  • Model: 데이터베이스 저장(백엔드)
  • View: 사용자가 보는 화면 (프론트엔드)
  • Controller: Model-View 사이에서 로직 관리 + Route 후 실행 내용 (백엔드)
    +) 이 외에
  • Router: app.js에서 작성하던 get,post 관리
  • app.js: 서버 개설, 개방 및 router 사용

1. 홈 - 전체 댓글 - 개별 댓글 페이지

MVC 패턴 적용 전

기존 app.js 구성

  • 기본 세팅
  • express 호출
  • app 객체 생성
  • 포트 번호 지정
  • 뷰 엔진 사용 선언 및 경로 지정
  • 인코딩
  • json 사용 선언
  • DB 선언
  • 페이지 라우팅 + 실행 동작 정의(parameter 전달 포함)
  • 에러 페이지 라우팅

MVC 패턴 적용 후

Model, Views, Controller, Router 폴더 생성

app.js

route 폴더 내 index 모듈 호출해 indexRouter에 저장

index.js < Routes

기능 : 페이지 라우팅

express 호출
controller 모듈 선언

  • controller 폴더 내 Cmain 모듈 호출해 저장
    Router 객체 생성
    페이지 연결 및 각 페이지에 controller 의 comments,comment를 parameter로 전달

Comment.js < Model

DB를 반환하는 getCommentAll 모듈 선언

Cmain.js < Controller

기능: 라우팅 되었을 때 실행 동작 선언

model 폴더 내 Comment DB 모듈 호출해 저장

main 모듈 선언

  • index.ejs를 render함

comments 모듈 선언

  • comments 페이지 render
  • Comment 모듈의 getCommentAll 가져와 comments로 전달

comment 모듈 선언

  • cmtId= 전달받은 id를 정수로 변환해 저장한 변수
  • Comment 모듈의 getCommentAll()을 통해 comments 함수에 저장(댓글 목록 불러오기)
  • cmtId 배열에 없는 id일 시 404 페이지 render

각 id값에 맞는 comment페이지 render 및 parameter로 comment 저장

comments.ejs < Views

각 DB 내 값을 활용 해 댓글 목록 보여주기
개별 댓글 (comment)로의 이동 링크

comment.ejs < Views

개별 댓글 보여주기

2. 로그인 페이지 - 값에 따라 한 페이지 내 결과 나타남

MVC 패턴 적용

Model, Views, Controller, Router 폴더 생성

app.js

  • 기본 세팅
  • express 호출
  • app 객체 생성
  • 포트 번호 지정
  • 뷰 엔진 사용 선언 및 경로 지정
  • 인코딩
  • json 사용 선언
  • route 폴더 내 router 모듈 호출해 indexRouter에 저장
  • 메인페이지 라우팅

router.js < Routes

기능 : 페이지 라우팅

express 호출
ctrlr 모듈 선언

  • controller 폴더 내 Cmain 모듈 호출해 저장
    Router 객체 생성을 통한 router 모듈 선언 + 내보내기
    라우팅
  • ctrlr 파일 내 main 모듈 get
  • axios 결과 전달을 위한 /loginForm post + ctrlr 파일 내 result 연결

db.js < Model

id, pw 값들을 가진 DB를 반환하는 getInfo 함수 내보내기

Cmain.js < Controller

기능: 라우팅 되었을 때 실행 동작 선언

model 폴더 내 DB 모듈 호출해 저장

main 모듈 선언

  • login.ejs를 render함

result 모듈 선언

  • 로그인 결과(성공|실패)를 보여줄 모듈
  • DB의 getInfo 함수를 통해 모든 db를 불러옴 => 반복문 실행 => 사용자가 입력한 req.body의 id, pw와 일치하면 state: "Sucess" 를 parameter로 send
  • 안 일치하면 state: "Fail" send

도중 만난 [ERR_HTTP_HEADERS_SENT] 에러

주의할 점 :


    if ((req.body.id == info.id) & (req.body.pw == info.pw)) {
      // return res.send({ state: "Success" });
      res.send({ state: "Success" });
    } else {
      res.send({ state: "Fail" });
    }
  

초반엔 반복문 내 코드를 이렇게 작성했었다.
그러나 console창에

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:372:5)

라는 에러가 찍혔었고, 구글링을 했다.
참고링크1
참고링크2
이유는 return 없이는, 서버가 if문/else문에 해당하는 두개의 response를 보내려 하기 때문에 중복되어서 에러가 나는 것이었다.

따라서 코드를 다음과 같이 고쳤다.


  for (let info of DB.getInfo()) {
    console.log("info ", info);
    if ((req.body.id == info.id) & (req.body.pw == info.pw)) {
      return res.send({ state: "Success" });
    }
  }
  return res.send({ state: "Fail" });

=> 처음엔 return res.send({ state: "Fail" }); 이 부분을 왜 for문 밖에 써야 동작하는지 헷갈렸는데 이 코드를 for문 밖에 작성해 default로 지정하고, for문 내에서는 id,pw가 일치할 때에만 Success를 반환하게 만들어 잘 동작하는 것 같다.
for문 내에 쓰면 db에서 for문이 도는 순서에 따라 db내용과 일치하는 id,pw여도 로그인이 안 될 때가 있었다.

이렇게 (위는 사용자 입력값, 아래는 db 내 첫번째 값인데 둘 다 db에 포함되어 있는 값들이다.)

login.ejs < Views

로그인 폼 + 결과를 보여주는 페이지
axios post를 실행하는 페이지
loginForm의 name을 가진 form이 axios post를 실행하고, 이는 router.js에서 post되는 주소이다.
Cmain에서 전달되는 state 값을 통해 로그인 성공 | 실패를 보여준다.

3. 선택실습

변경 내용

db.js

지정된 id,pw,userName 모듈 작성 및 내보내기

exports.users =
  "apple//1234//사과사과//banana//4321//바나나나나//happy//qwer1234//해피해피";

login.ejs

동적 axios 전송 새로 작성 및 해당 함수 이름으로 button의 onClick 내용 수정

      <button type="button" onclick="axiosPost2()">LOGIN</button>
function axiosPost2() {
        const info = {
          id: form.id.value,
          pw: form.pw.value,
        };
        const { id, pw } = info;

        try {
          if (!form.id.value & !form.pw.value) {
            result.innerText = "ID와 PW는 필수 입력값입니다.";
          } else {
            axios({
              method: "post",
              url: "/loginForm",
              data: info,
            }).then(function (response) {
              const { data } = response;
              //console.log(data.state);
              const { state, userName } = data;
              //console.log(state);
              console.log(userName); 
              //response로 parameter들을 받아오므로 구조분해 할당 해야 값이 들어옴


              if (state === "Success") {
                result.innerText = `${userName}님 환영합니다.`;
                result.style.color = "blue";
              } else if (state === "Fail") {
                result.innerText = "로그인에 실패했습니다.";
                result.style.color = "red";
              }
            });
          }
        } catch (err) {
          result.innerText = err;
        }
      }

Cmain.js

동적 axios 전송 결과 (result2) 모듈 작성 및 내보내기 + 성공 시 userName을 parameter로 전달

exports.result2 = (req, res) => {
  usersArr = users.split("//");
  // console.log("usersArr", usersArr);

  // for (let i = 0; i <= usersArr.length; i += 3) {
  //   //id만 출력
  //   console.log("id ", i, usersArr[i]);
  // }
  // for (let i = 1; i <= usersArr.length; i += 3) {
  //   //pw만 출력
  //   console.log("pw ", i, usersArr[i]);
  // }
  // for (let i = 2; i <= usersArr.length; i += 3) {
  //   //userName만 출력
  //   console.log("userName ", i, usersArr[i]);
  // }
  for (let i = 0; i <= usersArr.length; i += 3) {
    console.log("id ", i, usersArr[i]);
    console.log("pw", i, usersArr[i + 1]);
    console.log("user input id", req.body.id);
    console.log("user input pw", req.body.pw);

    if ((req.body.id == usersArr[i]) & (req.body.pw == usersArr[i + 1])) {
      return res.send({ state: "Success", userName: usersArr[i + 2] });
    }
  }
  return res.send({ state: "Fail" });
};

router.js

새로 작성한 동적 axios 전송 결과 (result2) post

router.post("/loginForm", ctrlr.result2); //선택실습

0개의 댓글