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하고 userid를 uid라는 쿠키로 만들어 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>
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;