1) Node.js
1-1) 댓글 CRUD 구현 - back
1-2) 댓글 front 화면 구현(CRUD) - 모듈 및 class 활용
1) Node.js
1-1) 댓글 CRUD 구현 - back
back
server.js
const express = require("express");
const cors = require("cors");
const app = express();
const mysql = require("./models");
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(
cors({
origin: true,
credentials: true,
})
);
/*
`id`
`userid`
`content`
`register`
*/
app.get("/comments", async (req, res, next) => {
try {
const [response] = await mysql.query(
`SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment`
);
res.json(response);
} catch (e) {
next(e);
}
});
app.post("/comments", async (req, res, next) => {
try {
const userid = `web7722`;
const { content } = req.body;
// if (!userid) throw new Error("userid가 없습니다.");
if (!content) throw new Error("content 없음!");
const sql = `INSERT INTO Comment(userid, content) VALUES('${userid}','${content}')`;
const [{ insertId }] = await mysql.query(sql);
const [[response]] = await mysql.query(
`SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment WHERE id=${insertId}`
);
res.json(response);
} catch (e) {
next(e);
}
});
app.get("/comments/:id", async (req, res, next) => {
try {
const { id } = req.params;
const [[response]] = await mysql.query(
`SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment WHERE id=${id}`
);
res.json(response);
} catch (e) {
next(e);
}
});
/*
[
{
"fieldCount": 0,
"affectedRows": 1,
"insertId": 0,
"info": "Rows matched: 1 Changed: 1 Warnings: 0",
"serverStatus": 2,
"warningStatus": 0,
"changedRows": 1
},
null
]
*/
app.put("/comments/:id", async (req, res, next) => {
try {
const { id } = req.params; // DB 식별자
const { content } = req.body; // client한테 받은 데이터
const [{ changedRows }] = await mysql.query(
`UPDATE Comment SET content='${content}' WHERE id='${id}'`
);
if (changedRows <= 0)
throw new Error("수정된 데이터가 없습니다. id를 다시 확인해주세요!!");
res.json({ result: changedRows });
} catch (e) {
next(e);
}
});
// CRUD
// R --> SELECT
// C --> INSERT INTO
// U --> UPDATE SET
// D --> DELETE
/*
[
{
"fieldCount": 0,
"affectedRows": 0,
"insertId": 0,
"info": "",
"serverStatus": 2,
"warningStatus": 0
},
null
]
*/
app.delete("/comments/:id", async (req, res, next) => {
try {
const { id } = req.params;
const [{ affectedRows }] = await mysql.query(
`DELETE FROM Comment WHERE id=${id}`
);
// affectedRows의 데이터 타입은 "Number"이다!
if (affectedRows <= 0)
throw new Error("삭제된 데이터가 없습니다. id를 다시 확인해주세요!!");
res.json({ result: affectedRows });
} catch (e) {
next(e);
}
});
app.use((err, req, res, next) => {
res.status(500).json({
message: err.message,
});
});
app.listen(3000, () => {
console.log(`server start`);
});
models/index.js
const mysql = require("mysql2");
const pool = mysql
.createPool({
host: "127.0.0.1",
port: "3306",
user: "root",
password: "본인 MySQL 비밀번호",
database: "comments",
})
.promise();
module.exports = pool;
1-2) 댓글 front 화면 구현(CRUD) - 모듈 및 class 활용
모듈
webpack: "webpack"은 React 배울 때 필수사항이다!!
Node.js에서 자체 개발한 것이 바로 "CommonJS 모듈 시스템"이기에
대부분의 Javascript 코드에서 "require()", "module.exports"를 사용함!(안정성이 좋음!)
module
CommonJS 모듈 시스템: require(), module.exports
- 파일을 받아올 때는 "require()"를 사용함
- 파일을 보낼 때는 "module.exports"를 사용함
ES6, 7 모듈(신문법)
- import ... from ...
- export default {}
import express from 'express'
const express = require('express')
import mysql from './models/index.js'
const mysql = require('./models/index.js')
module.exports = {...}
export default {}
exports.add = () => {}
export const add = () => {}
class
class로 화면을 표현하는 것을 배워야 한다!
이유1) class를 작성하는 능력을 키우기 위함이다!
이유2) React 때 쉬어진다!
이유3) 디자인 패턴을 어느 정도 배울 수 있게 된다!(이를 통해 디자인 패턴이 어떤 용도로 사용되는지 알 수 있음!)
--> 22가지로 여러가지 패턴을 다 구현할 수 있다!
express도 class로 바꿔서 하는 것이 가능해진다!
+) switch 문
if (boolean) {};
const test = 'web7722';
switch(test) {
case "sangbeom":
// code block
break;
case "sang":
// code block
break;
default:
// code block
break;
}
switch(test) {
case "web7722":
case "web":
case "7722":
// code block
console.log('hello');
break;
case "sangbeom":
case "sang":
case "hello":
// code block
console.log('world');
break;
default:
console.log(null);
break;
}
front
server.js
const express = require("express");
const nunjucks = require("nunjucks");
const app = express();
app.set("view engine", "html");
nunjucks.configure("views", {
express: app,
});
app.use(express.static("public"));
app.use(express.json());
app.get("/", (req, res, next) => {
res.render("index.html");
});
app.listen(3005, () => {
console.log("server start");
});
views/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link href="/css/index.css" rel="stylesheet" />
</head>
<body>
<div>
<ul class="comment">
<li class="comment-form">
<form id="commentFrm">
<h4>
댓글쓰기
<span></span>
</h4>
<span class="ps_box">
<input
type="text"
placeholder="댓글 내용을 입력해주세요."
class="int"
name="content"
/>
</span>
<input type="submit" class="btn" value="등록" />
</form>
</li>
<li id="comment-list"></li>
</ul>
</div>
<template id="commentRow">
<ul class="comment-row" data-index="1">
<li class="comment-id"></li>
<li class="comment-content"></li>
<li class="comment-date"></li>
</ul>
</template>
<template id="content-baisc">
<span class="comment-update-btn">입력입력입력~~~</span>
<span class="comment-delete-btn">❌</span>
</template>
<template id="content-update">
<span>
<input type="text" class="comment-update-input" data-value="" />
</span>
<span class="comment-delete-btn">❌</span>
</template>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- <script src="/js/comment.js" type="text/javascript"></script> -->
<script src="/js/commentRequest.js" type="module"></script>
<script src="/js/request.js" type="module"></script>
<script src="/js/comment.js" type="module"></script>
<script src="/js/index.js" type="module"></script>
</body>
</html>
public/css/index.css
* {
margin: 0;
padding: 0;
}
ul,
li {
list-style: none;
}
.comment {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
padding: 30px;
width: 600px;
margin: 0 auto;
}
.comment > li {
margin-top: 20px;
}
.comment > li:nth-child(1) {
margin: 0px;
}
.comment-row {
display: flex;
justify-content: space-between;
flex-direction: row;
}
.comment-row {
margin-top: 20px;
width: 100%;
}
.comment-row > li:nth-child(2) {
flex-shrink: 0;
flex-grow: 1;
padding-left: 25px;
z-index: 1;
width: 100%;
}
.comment-row > li:nth-child(2) {
width: 85px;
}
.comment-form > form {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.comment-form > form > h4 {
width: 100%;
margin: 14px 0 14px 0;
}
.comment-content {
cursor: pointer;
word-break: break-all;
padding-right: 25px;
}
.ps_box {
display: block;
position: relative;
width: 80%;
height: 51px;
border: solid 1px #dadada;
padding: 10px 14px 10px 14px;
background: #fff;
box-sizing: border-box;
}
.ps_box > input {
outline: none;
}
.int {
display: block;
position: relative;
width: 100%;
height: 29px;
padding-right: 25px;
line-height: 29px;
border: none;
background: #fff;
font-size: 15px;
box-sizing: border-box;
z-index: 10;
}
.btn {
width: 18%;
padding: 18px 0 16px;
text-align: center;
box-sizing: border-box;
text-decoration: none;
border: none;
background: #333;
color: #fff;
font-size: 14px;
}
.comment-delete-btn {
display: inline-block;
margin-left: 7px;
cursor: pointer;
}
.comment-update-input {
border: none;
border-bottom: 1px solid #333;
font-size: 16px;
color: #666;
outline: none;
}
public/js/request.js
/*
// "export"는 값을 객체로 넣는 형식이다!!
export const a = 10;
// 위의 코드는 "exports.a = 10"과 동일함!!
// "export default"는 값 자체를 넣는 형식이기에 한 번 밖에 못쓴다!!
export default (a, b) => {
return a + b;
};
// 위의 코드는 "module.exports = (a, b) => {return a + b}"과 동일함!!
*/
// axios.get("http://localhost:3000/comments");
// "axios.create()"는 axios를 가지고 새로운 axios를 만든다는 의미이다!!
const request = axios.create({
baseURL: "http://localhost:3000",
withCredentials: true,
});
// request.get("/comments").then((data) => console.log(data));
export default request;
public/js/comment.js
/*
import sum, { a } from "/js/request.js";
console.log(a);
console.log(sum(1, 2));
console.log(axios);
*/
// import request from "/js/request.js";
// import request from "/js/commentRequest.js";
// request.get("/comments").then((data) => console.log(data));
// (async () => {
// const response = await request.find();
// console.log(response);
// })();
class Template {
constructor(templates) {
// this = {}
// return this
return this.create(templates);
}
create(templates) {
let obj = {};
for (const key in templates) {
obj[key] = this.clone(templates[key]);
}
return obj;
}
clone(selector) {
// const template = document.querySelector(selector);
// const clone = document.importNode(template.content, true);
// return clone;
return () => {
const template = document.querySelector(selector);
const clone = document.importNode(template.content, true);
return clone;
};
}
}
/*
// 생성자 함수
function Template1(templates) {
//this = {}
// return this
this.create = (templates) => {
let obj = {};
for (const key in templates) {
obj[templates[key]] = this.clone(`#${templates[key]}`);
}
return obj;
};
this.clone = (selector) => {
const template = document.querySelector(selector);
const clone = document.importNode(template.content, true);
return clone;
};
return this.create(templates);
}
*/
const config = {
row: "#commentRow",
basic: "#content-baisc",
update: "#content-update",
};
// const configArr = ["commentRow", "content-baisc", "content-update"];
const template = new Template(config);
// console.log(template.row === template.row); // true
// console.log(template.row() === template.row()); // false
export default template;
public/js/commentRequest.js
import request from "/js/request.js";
// 4개의 method를 만들 계획
class CommentRequest {
constructor(request) {
this.request = request;
}
async create(body) {
try {
const { data } = await this.request.post("/comments", body);
return data;
} catch (e) {
return null;
}
}
async find() {
try {
const { data } = await this.request.get("/comments");
return data;
} catch (e) {
return null;
}
}
async update(id, body) {
try {
const { data } = await this.request.put(`/comments/${id}`, body);
return data;
} catch (e) {
return null;
}
}
async delete(id) {
try {
const { data } = await this.request.delete(`/comments/${id}`);
return data;
} catch (e) {
return null;
}
}
}
const req = new CommentRequest(request);
/*
const req = {
request: axios(),
create: () => {},
find: () => {},
update: () => {},
delete: () => {}
}
*/
// req.create({ content: "hello world~~" }).then((data) => {
// console.log(data);
// });
// (async () => {
// const response1 = await req.create({ content: "hello world~~" });
// console.log(response1);
// const response2 = await req.find();
// console.log(response2);
// const response3 = await req.update(11, { content: "수정입니다~~" });
// console.log(response3);
// const response4 = await req.delete(33);
// console.log(response4);
// })();
export default new CommentRequest(request);
public/js/index.js
import request from "/js/commentRequest.js";
import template from "/js/comment.js";
const basic = (content) => {
const clone = template.basic();
const span = clone.querySelector(".comment-update-btn");
span.innerHTML = content;
return clone;
};
const row = ({ id, userid, content, register }) => {
const clone = template.row();
const ul = clone.querySelector("ul");
const li = clone.querySelectorAll("li");
ul.dataset.index = id;
li[0].innerHTML = userid;
li[1].append(basic(content));
li[2].innerHTML = register;
return ul;
};
const drawing = (ul) => {
const commentElement = document.querySelector("#comment-list");
commentElement.prepend(ul);
};
const init = async () => {
const list = await request.find();
if (list === null || list.length === 0) return;
for (const comment of list) {
const element = row(comment);
drawing(element);
}
};
const submitHandler = async (e) => {
e.preventDefault();
const { content } = e.target;
// console.log(content.value);
const newComment = await request.create({ content: content.value });
const newElement = row(newComment);
drawing(newElement);
e.target.reset();
content.focus();
};
const changeBtn = (contentEle, before) => {
const enterHandler = (e) => {
if (e.keyCode !== 13) return;
try {
if (e.target.value === "") throw new Error("빈칸은 하지 마세요~~");
if (e.target.value === before) return;
// request.update() id
// result: 1
contentEle.innerHTML = "";
const basicEle = basic(e.target.value);
contentEle.append(basicEle);
} catch (e) {
alert(e.message);
}
};
// 화면을 바꿔주기 구현
contentEle.innerHTML = "";
const clone = template.update();
const input = clone.querySelector("span > input");
input.value = before;
input.addEventListener("keyup", enterHandler);
contentEle.append(clone);
// enter를 쳤을 시 원래 상태로 되돌리기 구현
};
const deleteBtn = (ulElement) => {
try {
if (!confirm("삭제하시겠습니까?")) return;
ulElement.remove();
} catch (e) {
alert(e.message);
}
};
const clickHandler = (e) => {
const contentElement = e.target.parentNode;
const ulElement = contentElement.parentNode;
const { index: id } = ulElement.dataset;
switch (e.target.className) {
case "comment-update-btn":
changeBtn(contentElement, e.target.innerHTML);
break;
case "comment-delete-btn":
deleteBtn(ulElement);
break;
}
};
const events = ({ submitHandler, clickHandler }) => {
const form = document.querySelector("#commentFrm");
const list = document.querySelector("#comment-list");
form.addEventListener("submit", submitHandler);
list.addEventListener("click", clickHandler);
};
(async () => {
init();
events({
submitHandler,
clickHandler,
});
})();