본문 바로가기
학원에서 배운 것/node.js

KDT 5th 웹개발자 입문 수업 33일차

by 쿠리의일상 2023. 3. 22.

코드 리팩토링

기존 코드를 더 좋게 변경(개선)하는 것

  • 새로운 기능을 추가할 때
  • 예전 기술을 새로운 기술로 변경할 때
  • 기존 코드를 더 가독성이 좋거나 확장성이 좋은 코드로 변경할 때

 

 

현재 코드의 문제점은

컨트롤러와 라우터가 중복된 작업을 한다.

하나의 곳에서 처리가 가능한 일을 굳이 둘로 나누어 처리하고 있기 때문에

라우터는 주소 연결만 하도록 만들고, 회원 가입에 대한 처리를 컨트롤러에서 전부 처리하도록 리팩토링해준다.

 

// .eslintrc.js 수정하기

module.exports = {
  extends: ['airbnb-base'],
  rules: {
    'linebreak-style': 0,
    'no-console': 'off',
    'operator-linebreak': 'off',
    'consistent-return': 'off',
    'nonblock-statement-body-position': 'off',
    'import/no-extraneous-dependencies': 'off',
    curly: 'off',
  },
  parserOptions: {
    ecmaVersion: 'latest',
  },
  env: {
    es6: true,
  },
};

 

 

컨트롤러에서 결과를 라우터로 보내서 처리할 필요가 없다.

모든 처리 및 응답을 컨트롤러가 전담한다.

// 회원가입을 담당하던 컨트롤러 ( + 라우터랑 기능을 나눔)
userRegister: async (userInfo) => {
    try {
	    const client = await mongoClient.connect();
    	const user = client.db('kdt5').collection('user');

    	await user.insertOne(userInfo);
    	return true;
    } catch (err) {
    	console.error(err);
	}
}


// 리팩토링하여, 컨트롤러가 라우터의 기능을 전부 담당하여 함수로 처리
const mongoClient = require('./mongoConnect');
const REGISTER_SUCCESS_MSG =
  '회원 가입 성공! <br/><br/> <a href="/login">로그인으로 이동</a>';
const REGISTER_DUPLICATED_MSG =
  '회원가입 실패, 동일한 아이디가 존재합니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>';
const REGISTER_UNEXPECTED_MSG =
  '회원가입 실패, 알 수 없는 문제가 발생하였습니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>';

const registerUser = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const user = client.db('kdt5').collection('user');
    const duplicatedUser = await user.findOne({ id: req.body.id });

    if (duplicatedUser) return res.status(404).send(REGISTER_DUPLICATED_MSG);

    await user.insertOne(req.body);
    res.status(200).send(REGISTER_SUCCESS_MSG);
  } catch (err) {
    console.error(err);
    res.status(500).send(REGISTER_UNEXPECTED_MSG);
  }
};

** 보통 res에 메시지를 담는 것은 json() 을 사용하여 보내는 것이 관례인데,

현재 view engine으로 ejs 를 사용하고 있어서 html 을 바로 보내기 위해서 send() 를 사용하는 것임

 

기존 라우터는 결과를 보고 처리해주는 절차를 1차적으로 해준다.

리팩토링을 했기에 라우터는 라우팅 기능만 하도록 수정하면 된다.

const express = require('express');
const { registerUser } = require('../controllers/userController');

const router = express.Router();

router.get('/', (req, res) => {
  res.render('register');
});

// 리팩토링 후
router.post('/', registerUser);

// 리팩토링 전
// router.post('/', async (req, res) => {
//   // 중복 회원 찾기
//   const duplicatedUser = await userController.userCheck(req.body.id);
//   if (!duplicatedUser) {
//     const registerResult = await userController.userRegister(req.body);
//     if (registerResult) {
//       res
//         .status(200)
//         .send(
//           '회원 가입 성공! <br/><br/> <a href="/login">로그인으로 이동</a>'
//         );
//     } else {
//       res
//         .status(500)
//         .send(
//           '회원가입 실패, 알 수 없는 문제가 발생하였습니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>'
//         );
//     }
//   } else {
//     res
//       .status(400)
//       .send(
//         '회원가입 실패, 동일한 아이디가 존재합니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>'
//       );
//   }
// });

module.exports = router;

 

req 요청 프로퍼티

  • req.app : app 객체로 접근 가능
  • req.body : body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체
  • req.params : 파라미터의 데이터 
  • req.query : 쿼리스트링의 정보, ? 이후의 키=값
  • req.headers : header 값
  • req.cookies : cookie 값, cookie-parser 미들웨어가 사용된 쿠키값을 읽어올 수 있음
  • req.signedCookies : 서명된 쿠키들
  • req.ip : 프론트 아이피
  • req.protocol : 프로토콜 정보, http / https
  • req.url : 전체 url 정보
  • req.get(헤더명) : 헤더의 값을 가져온다 
  • req.router : 현재 라우터에 대한 정보
  • req.secure : 현재 요청의 보안 요청이 SSL(https) 이면 true, http면 false
    • if(req.secure) { //https } else { //http }
    • http 를 https 로 변경하는 법은,
    • res.redirect('https://' + req.headers.host + req.url); 
  • req.path : 클라이언트가 요청한 경로 (프로토콜, 호스트, 포트, 쿼리스트링을 제외)

 

res 응답 프로퍼티

  • res.end()
  • res.json(JSON)
  • res.redirect(url)
  • res.render(ejs/html)
  • res.send(데이터)
  • res.sendFile(경로)

각 라우터에서 반드시 한번만 사용할 것

 

 

 

몽고DB 게시판 컬렉션 만들기

명령어 말고 몽고db 아틀라스에서 바로 만드는 방법

// 리팩토링 후
// 모든 게시글 가져오기
const getAllArticle = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const board = await client.db('kdt5').collection('board');
    const articleCursor = board.find({});
    const ARTICLE = await articleCursor.toArray();
    res.render('db_board', {
      ARTICLE,
      articleCounts: ARTICLE.length,
      userID: req.session.userId,
    });
  } catch (err) {
    console.error(err);
    res.status(500).send(err.message + UNEXPECTED_MSG);
  }
};

// 리팩토링 전 - 모든 게시글 가져오기
// const boardDB = {
//   // 모든 게시글 가져오기
//   getAllArticle: (cb) => {
//     const selectQuery = 'SELECT * FROM mydb.board';
//     connection.query(selectQuery, (err, data) => {
//       if (err) throw err;
//       cb(data);
//     });
//   },
// 리팩토링 후
const writeArticle = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const board = await client.db('kdt5').collection('board');

    await board.insertOne({
      USERID: req.session.userId,
      TITLE: req.body.title,
      CONTENT: req.body.content,
    });
    res.status(200).redirect('/dbBoard');
  } catch (err) {
    console.error(err);
    res.status(500).send(err.message + UNEXPECTED_MSG);
  }
};
//   // 리팩토링 전 - 게시글 추가하기
//   writeArticle: (newArticle, cb) => {
//     const insertQuery = `INSERT INTO mydb.board (TITLE, CONTENT, USERID) VALUE ('${newArticle.title}', '${newArticle.content}', '${newArticle.userID}');`;
//     connection.query(insertQuery, (err, data) => {
//       if (err) throw err;
//       cb(data);
//     });
//   },

 

 

게시글 찾기 기능-> _id 를 이용

mongoDB의 모듈 ObjectId 클래스를 사용해야 한다.

_id 는 단순한 문자열로 보이지만 특정 의미를 가지고 있기에 ObjectId 클래스로 해독이 가능함!

const { ObjectId } = require('mongodb');

// 리팩토링 후 - 특정 게시글 찾기
const getArticle = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const board = await client.db('kdt5').collection('board');

    const findArticle = await board.findOne({
      _id: ObjectId(req.params.id),
    });
    res.render('db_board_modify', findArticle);
  } catch (err) {
    console.error(err);
    res.status(500).send(err.message + UNEXPECTED_MSG);
  }
};

여기서 주의할 점은 매개변수로 받아온 req.params.id 를 ObjectId() 로 감싸줘야 몽고db가 받아올 수 있는 것이다.

 

 

// 몽고DB 에 글 수정하기
const modifyArticle = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const board = await client.db('kdt5').collection('board');

    await board.updateOne(
      {
        _id: ObjectId(req.params.id),
      },
      {
        $set: {
          TITLE: req.body.title,
          CONTENT: req.body.content,
        },
      }
    );
    res.status(200).redirect('/dbBoard');
  } catch (err) {
    console.error(err);
    res.status(500).send(err.message + UNEXPECTED_MSG);
  }
};
// 몽고DB 에 글 삭제하기
const deleteArticle = async (req, res) => {
  try {
    const client = await mongoClient.connect();
    const board = await client.db('kdt5').collection('board');

    await board.deleteOne({
      _id: ObjectId(req.params.id),
    });
    res.status(200).redirect('dbBoard');
  } catch (err) {
    console.error(err);
    res.status(500).send(err.message + UNEXPECTED_MSG);
  }
};

 

 

Mongoose 모듈

MongoDB는 사용상 제약이 전혀 없어서 편리하지만, 제약이 없기에 생기는 문제가 있다.

 

약속해주었던 컬렉션에 다른 키를 가진 값이 들어가게 되면, 버그가 발생하게 된다.

만약 MySQL 이었다면 데이터를 삽입하는 순간 이미 제약 조건에 의해 데이터가 삽입이 안되어 데이터의 일관성이 유지되었을 것이다.

 

이러한 문제점에 대한 제약 사항을 추가해주고 MongoDB의 장점을 살려주는 것이 Mongoose 모듈이다.

(다만 속도 저하가 생길 수 있음)

 

object 들을 만들어서 data를 묶어서 사용하는 ODM(Object Data Mapping) 특성에 기인한다.

몽구스는 데이터를 만들고 관리하기 위하여 스키마를 만들고 그 스키마로 모델을 만들어준다.

 

스키마와 모델을 만드는 것을 통하여 data를 불러온 후, 그 데이터를 객화시키는데에 빠르고 그 객체를 수정함으로써 데이터에 수정을 할 수 있게 해준다.

 

npm i mongoose -S 로 설치

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/DB명');
const Schema = mongoose.Schema; // 스키마를 만들기 위해, 스키마 객체를 옮겨 담기

 

몽구스는 들어갈 데이터가 지닌 속성의 타입을 지정해준 뒤 집어넣어준다.

이 속성의 타입을 지정해주는 것이 스키마이며,

지정된 스키마로 객체를 만들고 그 객체를 문서화하여 컬렉션에 저장해주는 역할을 모델이 한다.