클라우드 14일차

soso·2024년 6월 27일

클라우드 부트캠프

목록 보기
16/77

node.js에서 MongoDB연동 게시판 만들기

초기 구성

express mongodb 드라이버 mysql2 드라이버 설치

npm init
npm i express mongodb mysql2

라우터 분리 시 매번 server.js를 통해 DB 연결을 하는 문제를 해결하기 위해 db_setup.js에 db연결 객체와 모듈 분리

db_setup.js
MongoDB와 MySQL 연결

const { MongoClient } = require("mongodb");
const mysql = require("mysql2");

let mongodb;
let mysqldb;

const setup = async () => {
  if (mongodb && mysqldb) {
    return { mongodb, mysqldb };
  }

  try {
    // 몽고DB 접속
    const mongoDbUrl = `mongodb+srv://admin:1234@cluster0.jjdzoie.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0`;
    
    const mongoConn = await MongoClient.connect(mongoDbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
    mongodb = mongoConn.db("myboard");
    console.log("몽고DB 접속 성공");

    // MySQL 접속
    mysqldb = mysql.createConnection({
      host: "localhost",
      user: "test",
      password: "1234",
      database: "myboard",
    });
    mysqldb.connect();
    console.log("MySQL 접속 성공");

    return { mongodb, mysqldb };
  } catch (err) {
    console.error("DB 접속 실패", err);
    throw err;
  }
};

module.exports = setup;

server.js
db연결이 된 후 http server을 띄우기 위해 콜백 함수에 async/await 추가

const setup = require("./db_setup");
const express = require("express");

const app = express();

app.use('/', require('./routes/account.js')); //라우팅 포함하는 코드

app.listen(8080, async () => {				//콜백 함수에 async/await 추가
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});  

라우터 분리

server.js에서 모든 라우터들을 관리하지 않고 도메인별로 CRUD 기능을 분리해 routes폴더에 등록

account.js
routes라는 폴더를 만들고 그 안에 작성
홈 화면과 로그인 화면을 리턴해주는 라우팅 추가

const router = require("express").Router();
const setup = require("../db_setup");

router.get("/", async (req, res) => {
    console.log("GET / 처리 시작 ");
  try {
    const { mongodb, mysqldb } = await setup();			//setup()이 여러 번 호출되지만
    res.send("홈화면 : 데이터베이스 사용 가능");
  } catch (err) {
    res.status(500).send("데이터베이스 연결 실패");
  }
});

router.get("/login", async (req, res) => {
  console.log("GET /login 처리 시작 ");
  try {
    const { mongodb, mysqldb } = await setup();			//DB연결이 반복적으로 되지는 않음
    res.send("로그인 화면 : 데이터베이스 사용 가능");
  } catch (err) {
    res.status(500).send("데이터베이스 연결 실패");
  }
});


module.exports = router;

ejs 엔진 설치

npm i ejs

server.js
ejs 엔진 설정
express에서는 ejs파일의 기본 저장 디렉토리명이 views

const setup = require("./db_setup");
const express = require("express");

const app = express();

// 'templates' 폴더를 뷰 디렉토리로 설정
//const path = require("path"); // path 모듈 임포트
//app.set('views', path.join(__dirname, 'templates'));// templates는 임의의 폴더명
//app.set('view engine', 'ejs'); // 이 설정은 없어도 무방

//라우팅 포함하는 코드
app.use('/', require('./routes/account.js')); 

app.listen(8080, async () => {
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});

views/menu.html

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
  

<nav class="navbar navbar-expand-lg bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="/">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/post/list">게시글목록</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="/account/enter">회원가입</a>
          </li>
          <li class="nav-item">
            <a class="nav-link disabled">Disabled</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>

views/index.ejs
menu.html을 포함

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Home</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
  </head>
  <body>
    <%- include('menu.html') %>			//menu.html을 포함
      
    <h1>홈입니다.</h1>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
  </body>
</html>

server.js
'/'로 요청 시 index.ejs로 렌더링 하도록 라우팅 추가

const setup = require("./db_setup");
const express = require("express");

const app = express();

// 'templates' 폴더를 뷰 디렉토리로 설정
//const path = require("path"); // path 모듈 임포트
//app.set('views', path.join(__dirname, 'templates'));// templates는 임의의 폴더명
app.set('view engine', 'ejs'); // 이 설정은 없어도 무방

//라우팅 
app.get("/", (req, res) => {				//index.ejs로 렌더링
    res.render("index.ejs");
  });
  
app.use('/', require('./routes/account.js')); 

app.listen(8080, async () => {
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});

routes/account.js
'/account/enter'로 요청 시 회원가입 화면 렌더링 하도록 라우팅 추가

const router = require("express").Router();
const setup = require("../db_setup");

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

module.exports = router;

views/enter.ejs

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Home</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
  </head>
  <body>
    <%- include('menu.html') %>
      
      <div class = "container mt-4">
        <h1>회원가입</h1>
        <form action = "/account/save" method="post">		// submit 시 '/account/save'로 post 요청 하는 form
            <div class = "form-group">
            <label>아이디</label>
            <input type="text" name = "userid" class = "form-control">
            </div><p></p>
            
            <div class="form-group">
            <label>비밀번호</label>
            <input type="password"  class="form-control" name ="userpw">
            </div><p></p>  
            
            <div class="form-group">
              <label>이름</label>
              <input type="text"  class="form-control" name ="username">
            </div><p></p> 

            <button type = "submit" class="btn btn-warning" style="float:right">회원가입</button>
        </form>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
  </body>
</html>

회원가입

body-parser 설치
사용자 파라미터 파싱용

npm i body-parser

server.js
body-parser 사용 설정

const setup = require("./db_setup");
const express = require("express");
const app = express();

const bodyParser = require("body-parser");				//body-parser 사용 설정
app.use(bodyParser.urlencoded({ extended: true }));

// 'templates' 폴더를 뷰 디렉토리로 설정
//const path = require("path"); // path 모듈 임포트
//app.set('views', path.join(__dirname, 'templates'));// templates는 임의의 폴더명
app.set('view engine', 'ejs'); // 이 설정은 없어도 무방

//라우팅 
app.get("/", (req, res) => {
    res.render("index.ejs");
  });
  
app.use('/', require('./routes/account.js')); 

app.listen(8080, async () => {
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});

account.js
request body 확인

const router = require("express").Router();
const setup = require("../db_setup");

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

router.post("/account/save", (req, res) => {		//request body 확인
  console.log(req.body);
  res.send("save ok");
});

module.exports = router;

sha256 모듈 설치
회원가입 시 패스워드 해싱용

npm i sha256

account.js
ID 중복 검사 후 salt를 생성해 pw 암호화를 하고 가입을 완료
salt는 MySQL에 저장

create table UserSalt(
userid varchar(20) primary key,
salt varchar(500) not null
);
const router = require("express").Router();
const setup = require("../db_setup");

const sha = require("sha256");

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

router.post("/account/save", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        res.render("enter.ejs", { data: { msg: "ID가 중복되었습니다" } });
      } else {
        const generateSalt = (length = 16) => {			//req.body.userid값이 mongodb값에 없을 시 salt 생성
          const crypto = require('crypto');
          return crypto.randomBytes(length).toString("hex");
        };

        const salt = generateSalt();
        req.body.userpw = sha(req.body.userpw + salt);	//req.body.userpw값과 salt를 더해 sha로 암호화
        mongodb
          .collection("account")
          .insertOne(req.body)
          .then((result) => {
            if (result) {
              console.log("회원가입 성공");

              //MySQL에 salt를 저장
              const sql = `INSERT INTO UserSalt (userid, salt)
                    VALUES (?,?)`;
                    mysqldb.query(sql, [req.body.userid, salt], (err, result2) => {
                if (err) {
                  console.log(err);
                } else {
                  console.log("salt 저장 성공");
                }
              });
              res.redirect("/");
            } else {
              console.log("회원가입 fail");
              res.render("enter.ejs", { data: { alertMsg: "회원가입 fail" } });
            }
          })
          .catch((err) => {
            console.log(err);
            res.status(500).send();
          });
      }
    })
    .catch((err) => {
      console.log(err);
      res.status(500).send();
    });
});

module.exports = router;

enter.ejs
ID중복 메세지나 경고 메세지 출력

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Home</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
  </head>
  <body>
    <%- include('menu.html') %>

    
      <% if(typeof data != 'undefined' && data.alertMsg){ %>	//ID중복 메세지나 경고 메세지 출력
        <script>
          alert(`<%= data.alertMsg %>`) ;
        </script>            
      <% } %>
  

    <div class="container mt-4">
      <h1>회원가입</h1>
      <form action="/account/save" method="post">
        <div class="form-group">
          <label>아이디</label>
          <input type="text" name="userid" class="form-control" />
          <% if(typeof data != 'undefined' && data.msg){ %>		//ID중복 메세지나 경고 메세지 출력
          <span class="text-danger"> <%= data.msg %></span>
          <% } %>
        </div>
        <p></p>

        <div class="form-group">
          <label>비밀번호</label>
          <input type="password" class="form-control" name="userpw" />
        </div>
        <p></p>

        <div class="form-group">
          <label>이름</label>
          <input type="text" class="form-control" name="username" />
        </div>
        <p></p>

        <button type="submit" class="btn btn-warning" style="float: right">회원가입</button>
      </form>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
  </body>
</html>

로그인

menu.html
적당한 위치에 login form이 보이도록 구성
브라우저를 새로고침해도 로그인이 풀려보이는 문제를 해결하기 위해 uid라는 쿠키가 있으면 logout 버튼 가시화하도록 JavaScript 구성

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />

<nav class="navbar navbar-expand-lg bg-light">
  <div class="container-fluid">
    <span id="loginSpan">
    <form action="/account/login" method="post">		//login form
        ID <input size="3" name="userid"> 
        PW <input size="3" name="userpw"> 
        <input type="submit" value="login" class="btn btn-outline-warning">
      </form>
    </span>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="/">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/post/list">게시글목록</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/account/enter">회원가입</a>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled">Disabled</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>

<script>
  const uid = $.cookie("uid");

  if (uid) {								//uid라는 쿠키가 있으면 logout 버튼 가시화
    //$("#loginA").addClass("disabled");
    $("#loginSpan").html(`${uid} <button class="btn btn-warning">logout</button>`);
  }

  function logout() {
    $.removeCookie("uid", { path: "/" });
    location.href = "/account/logout";
  }
</script>

express-session cookie-parser 설치

npm i express-session cookie-parser

server.js
session과 cookie 설정

const setup = require("./db_setup");
const express = require("express");
const app = express();

const session = require("express-session");		//session 설정
app.use(
  session({
    secret: "암호화키",
    resave: false,
    saveUninitialized: false,
  })
);

const cookieParser = require("cookie-parser");	//cookie 설정
app.use(cookieParser());

const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));

// 'templates' 폴더를 뷰 디렉토리로 설정
//const path = require("path"); // path 모듈 임포트
//app.set('views', path.join(__dirname, 'templates'));// templates는 임의의 폴더명
app.set('view engine', 'ejs'); // 이 설정은 없어도 무방

//라우팅 
app.get("/", (req, res) => {
    res.render("index.ejs");
  });
  
app.use('/', require('./routes/account.js')); 

app.listen(8080, async () => {
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});

account.js
salt를 찾아와서 pw를 암호화 한 뒤 비교하는 로직 구성
로그인 성공 시 user 정보를 serialize하고 useriduid라는 쿠키로 만들어 response에 전송
index.ejs가 브라우저에서 처리될 때 uid라는 쿠키 값이 있는 것을 보고 logout 버튼을 그림

const router = require("express").Router();
const setup = require("../db_setup");

const sha = require("sha256");

router.post("/account/login", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        const sql = `SELECT salt FROM UserSalt 			//salt를 찾아와서
                    WHERE userid=?`;
        mysqldb.query(sql, [req.body.userid], (err, rows, fields) => {
          const salt = rows[0].salt;
          const hashPw = sha(req.body.userpw + salt);	//pw를 암호화 한 뒤
          //  console.log(hashPw);
          if (result.userpw == hashPw) {				//비교
            req.body.userpw = hashPw;
            req.session.user = req.body;				//user 정보 serialize
            //console.log(req.session.user);
            res.cookie("uid", req.body.userid);			//userid를 uid라는 쿠키로 만듦
            res.render("index.ejs");
          } else {
            //res.send("login fail");
            res.render("login.ejs");
          }
        });
      } else {
	      // login fail
        res.render('login.ejs');
      }
    })
    .catch((err) => {
	      // login fail
        res.render('login.ejs');
    });
});

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

router.post("/account/save", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        res.render("enter.ejs", { data: { msg: "ID가 중복되었습니다" } });
      } else {
        const generateSalt = (length = 16) => {
          const crypto = require("crypto");
          return crypto.randomBytes(length).toString("hex");
        };

        const salt = generateSalt();
        req.body.userpw = sha(req.body.userpw + salt);
        mongodb
          .collection("account")
          .insertOne(req.body)
          .then((result) => {
            if (result) {
              console.log("회원가입 성공");

              //MySQL에 salt를 저장
              const sql = `INSERT INTO UserSalt (userid, salt)
                    VALUES (?,?)`;
              mysqldb.query(sql, [req.body.userid, salt], (err, result2) => {
                if (err) {
                  console.log(err);
                } else {
                  console.log("salt 저장 성공");
                }
              });
              res.redirect("/");
            } else {
              console.log("회원가입 fail");
              res.render("enter.ejs", { data: { alertMsg: "회원가입 fail" } });
            }
          })
          .catch((err) => {
            console.log(err);
            res.status(500).send();
          });
      }
    })
    .catch((err) => {
      console.log(err);
      res.status(500).send();
    });
});

module.exports = router;

account.js
logout 처리를 할 때 session 파기

const router = require("express").Router();
const setup = require("../db_setup");

const sha = require("sha256");

router.get("/logout", (req, res) => {		//logout 시 session 파기
  console.log("로그아웃");
  req.session.destroy();
  res.render("index.ejs");
});

router.post("/account/login", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        const sql = `SELECT salt FROM UserSalt 
                    WHERE userid=?`;
        mysqldb.query(sql, [req.body.userid], (err, rows, fields) => {
          const salt = rows[0].salt;
          const hashPw = sha(req.body.userpw + salt);
          //  console.log(hashPw);
          if (result.userpw == hashPw) {
            req.body.userpw = hashPw;
            req.session.user = req.body;
            //console.log(req.session.user);
            res.cookie("uid", req.body.userid);
            res.render("index.ejs");
          } else {
            //res.send("login fail");
            res.render("login.ejs");
          }
        });
      } else {
      }
    })
    .catch((err) => {
      console.log(err);
      res.status(500).send();
    });
});

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

router.post("/account/save", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        res.render("enter.ejs", { data: { msg: "ID가 중복되었습니다" } });
      } else {
        const generateSalt = (length = 16) => {
          const crypto = require("crypto");
          return crypto.randomBytes(length).toString("hex");
        };

        const salt = generateSalt();
        req.body.userpw = sha(req.body.userpw + salt);
        mongodb
          .collection("account")
          .insertOne(req.body)
          .then((result) => {
            if (result) {
              console.log("회원가입 성공");

              //MySQL에 salt를 저장
              const sql = `INSERT INTO UserSalt (userid, salt)
                    VALUES (?,?)`;
              mysqldb.query(sql, [req.body.userid, salt], (err, result2) => {
                if (err) {
                  console.log(err);
                } else {
                  console.log("salt 저장 성공");
                }
              });
              res.redirect("/");
            } else {
              console.log("회원가입 fail");
              res.render("enter.ejs", { data: { alertMsg: "회원가입 fail" } });
            }
          })
          .catch((err) => {
            console.log(err);
            res.status(500).send();
          });
      }
    })
    .catch((err) => {
      console.log(err);
      res.status(500).send();
    });
});

module.exports = router;

account.js
로그인이 틀릴 시 alert

const router = require("express").Router();
const setup = require("../db_setup");

const sha = require("sha256");

router.get("/logout", (req, res) => {
  console.log("로그아웃");
  req.session.destroy();
  res.render("index.ejs");
});

router.post("/account/login", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        const sql = `SELECT salt FROM UserSalt 
                    WHERE userid=?`;
        mysqldb.query(sql, [req.body.userid], (err, rows, fields) => {
          const salt = rows[0].salt;
          const hashPw = sha(req.body.userpw + salt);
          //  console.log(hashPw);
          if (result.userpw == hashPw) {
            req.body.userpw = hashPw;
            req.session.user = req.body;//serialize
            
            res.cookie("uid", req.body.userid);
            res.render("index.ejs");
          } else {
            res.render("index.ejs", {data:{alertMsg:'다시 로그인 해주세요'}});	//db의 userpw값과
            																//암호화한 req.body.userpw값이
                                                                            //다를 시 alert
          }
        });
      } else {
        res.render("index.ejs", {data:{alertMsg:'다시 로그인 해주세요'}});	//db와 일치하는 id값이 없을 시 alert
      }
    })
    .catch((err) => {
      res.render("index.ejs", {data:{alertMsg:'다시 로그인 해주세요'}});	//error 시 alert
    });
});

router.get("/account/enter", (req, res) => {
  res.render("enter.ejs");
});

router.post("/account/save", async (req, res) => {
  const { mongodb, mysqldb } = await setup();
  mongodb
    .collection("account")
    .findOne({ userid: req.body.userid })
    .then((result) => {
      if (result) {
        res.render("enter.ejs", { data: { msg: "ID가 중복되었습니다" } });
      } else {
        const generateSalt = (length = 16) => {
          const crypto = require("crypto");
          return crypto.randomBytes(length).toString("hex");
        };

        const salt = generateSalt();
        req.body.userpw = sha(req.body.userpw + salt);
        mongodb
          .collection("account")
          .insertOne(req.body)
          .then((result) => {
            if (result) {
              console.log("회원가입 성공");

              //MySQL에 salt를 저장
              const sql = `INSERT INTO UserSalt (userid, salt)
                    VALUES (?,?)`;
              mysqldb.query(sql, [req.body.userid, salt], (err, result2) => {
                if (err) {
                  console.log(err);
                } else {
                  console.log("salt 저장 성공");
                }
              });
              res.redirect("/");
            } else {
              console.log("회원가입 fail");
              res.render("enter.ejs", { data: { alertMsg: "회원가입 fail" } });
            }
          })
          .catch((err) => {
            console.log(err);
            res.status(500).send();
          });
      }
    })
    .catch((err) => {
      console.log(err);
      res.status(500).send();
    });
});

module.exports = router;

index.ejs
account.js에서 넘어 온 alertMsg가 전달 되었을 때 script 생성하도록 구성

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Home</title>
  </head>
  <body>
    <%- include('menu.html') %>
      
    <% if(typeof data != 'undefined' && data.alertMsg){ %>	//alertMsg 전달 시 script 생성
      <script>
        alert(`<%= data.alertMsg %>`) ;
      </script>            
    <% } %>
    
    <h1>홈입니다.</h1>    

  </body>
</html>

로그인 된 사용자만 글목록 보기

menu.html
'게시글목록' 링크를 disabled 상태였다가 로그인 후에 사용 가능 상태로 변환

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />

<nav class="navbar navbar-expand-lg bg-light">
  <div class="container-fluid">
    <span id="loginSpan">
      <form action="/account/login" method="post">
        ID <input size="3" name="userid"> 
        PW <input size="3" name="userpw"> 
        <input type="submit" value="login" class="btn btn-outline-info">
      </form>
    </span>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="/">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled logined" href="/post/list">게시글목록</a>	//disabled
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/account/enter">회원가입</a>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled">Disabled</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>

<script>
  const uid = $.cookie("uid");

  if (uid) {								//로그인 시 disabled 클래스 삭제
    $(".logined").removeClass("disabled");
    $("#loginSpan").html(`${uid} <button class="btn btn-warning">logout</button>`);
  }

  function logout() {
    $.removeCookie("uid", { path: "/" });
    location.href = "/account/logout";
  }
</script>

server.js
menu.html에서 '게시글목록'을 클릭하면 게시글이 보여지도록 라우팅 하기 위해 라우터 등록

const setup = require("./db_setup");
const express = require("express");
const app = express();

const session = require("express-session");
app.use(
  session({
    secret: "암호화키",
    resave: false,
    saveUninitialized: false,
  })
);

const cookieParser = require("cookie-parser");
app.use(cookieParser());

const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));

// 'templates' 폴더를 뷰 디렉토리로 설정
//const path = require("path"); // path 모듈 임포트
//app.set('views', path.join(__dirname, 'templates'));// templates는 임의의 폴더명
app.set('view engine', 'ejs'); // 이 설정은 없어도 무방

//라우팅 
app.get("/", (req, res) => {
    res.render("index.ejs");
  });
  
app.use('/', require('./routes/account.js')); 
app.use('/', require('./routes/post.js')); 				//라우터 등록

app.listen(8080, async () => {
    await setup();
    console.log("8080 서버가 준비되었습니다...");
});

routes/post.js
'/post/list'요청 시 list.ejs로 렌더링

const router = require("express").Router();
const setup = require("../db_setup");

const sha = require("sha256");

router.get("/post/list", async (req, res) => {
  const { mongodb } = await setup();
  list(mongodb, req, res);
});

function list(mongodb, req, res) {					//forward
  mongodb
    .collection("post")
    .find()
    .toArray()
    .then((result) => {
      //console.log(result);
      res.render("list.ejs", { data: result });
    });
}

module.exports = router;

views/list.ejs
게시글 목록 페이지 생성

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Home</title>
    </head>
  <body>
    
    <%- include('menu.html') %>
      
      <table class = "table table-hover table-striped text-center container mt-4" style = "border: 1px solid;">
        <thead>
          <tr>         
            <th>제목</th>
            <th>작성일</th>
            <th>삭제</th>
          </tr>
        </thead>
        <tbody>
        <% for(let i = 0; i < data.length; i++){ %>
          <tr>         
            <td data-bs-toggle="modal" data-bs-target="#myModal">
              <a href="#" onclick="modal_content(`<%= data[i]._id %>`,`<%= data[i].title %>`,`<%= data[i].content %>`, `<%= data[i].date %>`)"><%= data[i].title %></a></td>
            <td><%= data[i].date %></td>
            <td><button class = 'delete btn btn-outline-danger' data-id = '<%= data[i]._id %>'>삭제</button></td>
          </tr>
        <% } %>
        </tbody>            
        </table>

        <div class="container my-3">
          <button class=" enter btn btn-primary" style="float:right;">글쓰기</button>
        </div>


        <script>
          $('.enter').click(function(){
            location.href='/enter';
          });

              let modalData;
              
              function modal_content(_id, title, content, date) {                
                modalData={_id, title, content, date};
                $('#postTitle').text(`${modalData.title}`);                
                $('#postContent').text(`${modalData.content}`);
                $('#postDate').text(`${modalData.date}`);
                $('#updateSpan').html(`<button type="button" class="btn btn-info">수정하러가기</button>`);
              }

              function editDisplay() {
                //console.log(modalData._id, modalData.title, modalData.content, modalData.date);
                $('#hiddenSpan').html(`<input  type='hidden' value='${modalData._id}' name='_id'>`);  
                $('#postTitle').html(`<input value='${modalData.title}' name='title'>`);                
                $('#postContent').html(`<textarea name='content'>${modalData.content}</textarea>`);
                $('#postDate').html(`<input type='date' value='${modalData.date}' name='someDate'>`);
                $('#updateSpan').html(`<button type="submit" class="btn btn-warning" >수정</button>`);
              }


        $('.delete').click(function(e){
          let sid = e.target.dataset.id;
          let item = $(this);
          $.ajax({
            type : 'post',
            url : '/delete',
            data : {_id : sid}
          }).done(function(result){
            //응답에 성공했을 때
            //location.reload();
            item.parent('td').parent('tr').remove();
          }).fail(function(xhr, textStatus, errorThrown){
            //응답에 실패했을 때
            console.log('게시물 삭제 실패');
            console.log(xhr, textStatus, errorThrown);
          })
        })   
        </script>

   
        <!-- The Modal -->
         <form action="/update" method="post">
          <span id="hiddenSpan"></span>
          <div class="modal" id="myModal">
            <div class="modal-dialog">
              <div class="modal-content">
          
                <!-- Modal Header -->
                <div class="modal-header">
                  제목 : <div><h4 class="modal-title" id="postTitle"></h4></div>        
                  <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
          
                <!-- Modal body -->
                <div class="modal-body" >
                  <div>
                    내용 : <span id="postContent"></span>
                  </div>
                  <p></p>
                  <div>
                    작성일 : <span id="postDate"></span>
                  </div>
                  
                </div>
          
                <!-- Modal footer -->
                <div class="modal-footer">  
                  <span id="updateSpan">
                    <button type="button" class="btn btn-info" onclick="editDisplay()">수정하러가기</button>
                  </span>      
                  
                  <button type="button" class="btn btn-danger" data-bs-dismiss="modal">Close</button>
                </div>
          
              </div>
            </div>
          </div>
         </form>

  </body>
</html>

postman으로 로그인 하지 않은 사용자가 게시판 볼 수 있는 문제 해결

post.js

const router = require("express").Router();
const setup = require("../db_setup");
const sha = require("sha256");

router.get("/post/list", async (req, res) => {
    if (req.session.user) {
        const { mongodb } = await setup();
        list(mongodb, req, res);
    } else {
        res.status(401).send({ message: '유효하지 않은 접근' });
    }
});

function list(mongodb, req, res) {
    mongodb
        .collection("post")
        .find()
        .toArray()
        .then((result) => {
            //console.log(result);
            res.render("list.ejs", { data: result });
        });
}

module.exports = router;

특정 라우터에서만 인증이 필요한 경우 위처럼 간단하게 라우터 내에서 세션 검사를 직접 수행할 수 있지만, 로그인 인증 검사가 필요한 페이지가 많다면 인증 미들웨어를 작성하여 여러 라우터에서 재사용하는 것이 유지보수성과 코드의 일관성을 높이는 데 좋다고 한다

authMiddleware.js
인증 미들웨어

const authMiddleware = (req, res, next) => {
    if (req.session.user) {
        next();
    } else {
        res.status(401).send({ message: '유효하지 않은 접근' });
    }
};

module.exports = authMiddleware;

post.js

const router = require("express").Router();
const setup = require("../db_setup");
const sha = require("sha256");
const authMiddleware = require("./authMiddleware");			//인증 미들웨어

// router.get("/post/list", async (req, res) => {
//     if (req.session.user) {
//         const { mongodb } = await setup();
//         list(mongodb, req, res);
//     } else {
//         res.status(401).send({ message: '유효하지 않은 접근' });
//     }
// });

router.get("/post/list", authMiddleware, async (req, res) => {
    try {
        const { mongodb } = await setup();
        list(mongodb, req, res);
    } catch {
        console.error(err);
        res.status(500).send({ message: '서버 오류' });
    }

});

function list(mongodb, req, res) {
    mongodb
        .collection("post")
        .find()
        .toArray()
        .then((result) => {
            //console.log(result);
            res.render("list.ejs", { data: result });
        });
}

module.exports = router;

0개의 댓글