[NodeJS] 게시판 구현 #1 / MySQL / 게시글 작성

Onam Kwon·2022년 8월 29일
1

Node JS

목록 보기
11/25

게시판 구현 #1

  • NodeJS MySQL을 이용해 로그인 기반 게시판을 구현했다.
  • 게시글 목록까지는 로그인 없이도 접근 가능하지만 게시글을 읽거나 쓰려면 로그인이 필요하다.
    • MongoDB 사용, 예전 게시글에 구현 설명 해놨습니다.
  • #1 게시글에서는 게시글 작성까지만 설명하겠습니다.

전체 코드

🔽 server.js 🔽


const express = require('express');
const app = express();

app.use(express.static(__dirname + ''));

// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));

// Cookies.
const cookieParser = require('cookie-parser');
app.use(cookieParser());

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// Socket.
const connectSocket = require('./controllers/chat/connectSocket');
// MongoDB.
const { connectMongoDB } = require('./models/connectMongoDB');

// Routers.
const homeRouter = require('./routes/homeRouter');
const game2048Router = require('./routes/2048Router');
const gameTetrisRouter = require('./routes/tetrisRouter');
const userRouter = require('./routes/userRouter');
const chatRouter = require('./routes/chatRouter');
const boardRouter = require('./routes/boardRouter');

const port = 80;
const server = app.listen(port, function() {
    console.log('Listening on '+port);
});

connectSocket(server);
connectMongoDB();

app.use('/', homeRouter);
app.use('/2048', game2048Router);
app.use('/tetris', gameTetrisRouter);
app.use('/user', userRouter);
app.use('/chat', chatRouter);
app.use('/board', boardRouter);

🔽 boardRouter.js 🔽

// boardRouter.js

const express = require("express");
const router = express.Router();

// Importing controller
const boardMiddleWare = require('../controllers/board/board.controller');

const auth = require("../controllers/authMiddleware");

// path: /board/
router.use('/', auth);

router.get('/', boardMiddleWare.showMain);
router.get('/', boardMiddleWare.searchByTitle);
router.get('/write', boardMiddleWare.boardWrite);
router.get('/:id', boardMiddleWare.showPost);
router.get('/:keyStroke', boardMiddleWare.autoComplete);
router.delete('/:id', boardMiddleWare.deleteArticle);
router.post('/article', boardMiddleWare.insertArticle);
router.get('/article/:id', boardMiddleWare.editArticle);
router.put('/article/:id', boardMiddleWare.submitEditedArticle);

module.exports = router;

🔽 board.controller.js 🔽

// board.controller.js

const express = require("express");
const app = express();

// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));

const path = require('path');

// allows you to ejs view engine.
app.set('view engine', 'ejs');
    
const dbMySQLModel = require('../../models/boardDBController');

function getUniqueElements(array) {
    let unique = array.filter((element, index) => {
        return array.indexOf(element) === index;
    });
    return unique;
}

function getTitlesIncludeString(titles, search) {
    let result = [];
    for(let i=0;i<titles.length;i++) {
        if(titles[i].includes(search)) result.push(titles[i]);
    }
    return result;
}

async function getPageItems(article_num, page, limit) {
    page = Math.max(1, parseInt(page));
    limit = Math.max(1, parseInt(limit));
    page = !isNaN(page)?page:1;
    limit = !isNaN(limit)?limit:10;
    let page_max = Math.ceil(article_num/limit);
    let range_max;
    const range_min = (page-1)*limit;
    (page === page_max) ? (range_max = article_num) : (range_max = page*limit)
    const obj = {
        page:page, 
        limit:limit,
        page_max:page_max, 
        range_min:range_min, 
        range_max:range_max
    }
    return obj;
}

// Main login page.
exports.showMain = async (req, res, next) => {
    if(req.query.search) return next();
    let user;
    if(!req.decoded) user = 'Guest';
    else user = req.decoded.id;
    let { search, page, limit } = req.query;
    const articles = await dbMySQLModel.showTable();
    const boardObject = await getPageItems(articles.length, page, limit);
    return res.render(path.join(__dirname, '../../views/board/board'), {
        articles:articles, 
        user: user,
        page_current:boardObject.page, 
        page_max:boardObject.page_max, 
        length:articles.length, 
        limit:boardObject.limit, 
        range_min:boardObject.range_min,
        range_max:boardObject.range_max,
        search:search
    });
}

exports.searchByTitle = async (req, res) => {
    let { search, page, limit } = req.query;
    let articles = await dbMySQLModel.getMatchingArticles(search);
    if(articles.length === 0) {
        return res.send("<script>alert('No matching article.'); window.location.href = '/board';</script>");
    }
    const boardObject = await getPageItems(articles.length, page, limit);
    return res.render(path.join(__dirname, '../../views/board/board'), {
        articles:articles,
        page_current:boardObject.page,
        page_max:boardObject.page_max, 
        length:articles.length, 
        limit:boardObject.limit, 
        range_min:boardObject.range_min,
        range_max:boardObject.range_max,
        search:search
    });
}

exports.showPost = async (req, res, next) => {
    if(req.query.keyStroke) return next();
    if(req.query.search) return next();
    const user = req.decoded;
    if(user) {
        const article_num = req.params.id;
        let article = await dbMySQLModel.showArticleByNum(article_num);
        return res.render(path.join(__dirname, '../../views/board/article'), {user:user, article: article});
    } else {
        return res.sendFile(path.join(__dirname, '../../views/board/login.html'));
    }
}

exports.autoComplete = async (req, res, next) => {
    if(req.query.search) return next();
    const keyStroke = req.query.keyStroke;
    const titles = await dbMySQLModel.getAllTitles();
    const result = await getTitlesIncludeString(titles, keyStroke);
    return res.status(200).send(result).end();
}

// Writing page.
exports.boardWrite = (req, res) => {
    const user = req.decoded;
    if(user) {
        return res.render(path.join(__dirname, '../../views/board/boardWrite'), {user:user});
    } else {
        return res.sendFile(path.join(__dirname, '../../views/board/login.html'));
    }
}

exports.insertArticle = async (req, res) => {
    const user = req.decoded;
    const { title, content } = req.body;
    if(user) {
        const author = user.id;
        await dbMySQLModel.insert(title, content, author);
        return res.status(200).send('Article has been posted.').end(); 
    } else {
        return res.sendFile(path.join(__dirname, '../../views/board/login.html'));
    }
}

exports.deleteArticle = async (req, res) => {
    const user = req.decoded;
    const { article_num } = req.body;
    const article = await dbMySQLModel.showArticleByNum(article_num);
    if(user.id === article.AUTHOR) {
        await dbMySQLModel.deleteByNum(article_num);
        return res.status(200).send('Article has been removed.').end(); 
    } else {
        return res.status(200).send('Account not matched.').end();
    }
}

exports.editArticle = async (req, res) => {
    const user = req.decoded;
    const article_num = req.params.id;
    const article = await dbMySQLModel.showArticleByNum(article_num);
    if(user.id === article.AUTHOR) {
        return res.render(path.join(__dirname, '../../views/board/editArticle'), {user:user, article:article});
    }
}

exports.submitEditedArticle = async (req, res) => {
    const user = req.decoded;
    const article_num = req.body.id;
    const title = req.body.title;
    const content = req.body.content;
    let article = await dbMySQLModel.showArticleByNum(article_num);
    const date_obj = new Date();
    article.UPDATE_DATE = date_obj.getFullYear() +"-"+ parseInt(date_obj.getMonth()+1) +"-"+ date_obj.getDate();
    await dbMySQLModel.editArticle(article_num, title, content, article.UPDATE_DATE);
    return res.status(200).send('Your article has been editied.');
}

🔽 boardDBController.js 🔽

// boardDBController.js
// connecting MySQL

const path = require('path');

require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 
const conn = require('../models/connectMySQL');

const util = require('util');
// node native promisify
const query = util.promisify(conn.query).bind(conn);

function convertDateFormat(date) {
    date = date.toLocaleString('default', {year:'numeric', month:'2-digit', day:'2-digit'});
    let year = date.substr(6,4);
    let month = date.substr(0,2);
    let day = date.substr(3,2);
    let convertedDate = `${year}-${month}-${day}`;
    return convertedDate;
}

function convertTableDateFormat(table) {
    for(let i=0;i<table.length;i++) {
        table[i].POST_DATE = convertDateFormat(table[i].POST_DATE);
        table[i].UPDATE_DATE = convertDateFormat(table[i].UPDATE_DATE);
    }
    return table;
}

function convertArticleDateFormat(article) {
    article.POST_DATE = convertDateFormat(article.POST_DATE);
    article.UPDATE_DATE = convertDateFormat(article.UPDATE_DATE);
    return article;
}

exports.showTable = async () => {
    let table = await query("SELECT * FROM BOARD ORDER BY BOARD_NO DESC;");
    table = convertTableDateFormat(table);
    return table;
}

exports.getAllTitles = async () => {
    let titles = await query("SELECT TITLE FROM BOARD ORDER BY BOARD_NO DESC;");
    for(let i=0;i<titles.length;i++) titles[i] = titles[i].TITLE;
    return titles;
}

exports.getMatchingArticles = async (title) => {
    let articles = await query("SELECT * FROM BOARD WHERE TITLE LIKE '%"+title+"%';");
    return convertTableDateFormat(articles);
}

exports.showArticleByNum = async (article_num) => {
    const sql = "SELECT * FROM BOARD WHERE BOARD_NO="+article_num+";";
    let article = await query(sql);
    article = article[0];
    article = convertArticleDateFormat(article);
    return article;
}

exports.insert = async (title, content, author) => {
    // Query to insert multiple rows
    let query = `INSERT INTO BOARD (TITLE, content, POST_DATE, UPDATE_DATE, AUTHOR) VALUES ?;`;
    
    const date_obj = new Date();
    let post_date = date_obj.getFullYear() +"-"+ parseInt(date_obj.getMonth()+1) +"-"+ date_obj.getDate();
    const update_date = post_date;
    // Values to be inserted
    let values = [
        [title, content, post_date, update_date, author]
    ];
    // Executing the query
    await conn.query(query, [values]);
}

exports.deleteByNum = async (article_num) => {
    let query = `DELETE FROM BOARD WHERE BOARD_NO=`+article_num+`;`;
    await conn.query(query);
}

exports.editArticle = async (article_num, title, content, update) => {
    let query = "UPDATE BOARD SET TITLE='"+title+"', content='"+content+"', UPDATE_DATE='"+update+"' WHERE BOARD_NO="+article_num+";";
    await conn.query(query);
}

🔽 board.ejs 🔽

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<link rel="stylesheet" type="text/css" href="/public/css/styles.css">
		<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
		<title>Onam Kwon</title>
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;"></body>
		<!-- Bootstrap CSS
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;">
		 -->
	</head>
	
	<body>
		<div class="navbar">
			<a class="active" href="/">Home</a> 
			<a href="/dev">Dev</a> 
			<a href="/private">Private</a> 
			<a href="/user">My page</a>
		</div>  
		<div class="wrapper">
			<ul class="item-list mySelf">
				<ul class="about">
                    <div>
                        <h1>
                            <a href="/board" style="color: white; text-decoration: none; display: inline-block;"> Board </a>
                            <a style="display: inline-block; float: right; margin-right: 3%;"> Welcome <%= user %>! </a>
                        </h1>
                        <div >
                            <input type="text" list="titles" id="search" name="search" placeholder="Title: " oninput="detectKeystroke()" style="display: inline-block;"/>
                            <datalist id="titles">

                            </datalist>
                            <button type="button" id="searchButton" onclick="search()">Search</button> <br><br>
                        </div>
                        <table id="board">
                            <tr>
                                <th style="height: 6vh; width: 15%;"> Article Number </th>
                                <th style="height: 6vh;"> Title </th>
                                <th style="height: 6vh; width: 10%;"> Posted date </th>
                                <th style="height: 6vh; width: 10%;"> Author </th>
                            </tr>
                            <% for(let i=range_min; i< range_max; i++) { %>
                                <tr>
                                    <td style="height: 5vh;"><%= articles[i].BOARD_NO %></td>
                                    <td style="height: 5vh;"><a href="/board/<%= articles[i].BOARD_NO %>"><%= articles[i].TITLE %></a></td>
                                    <td style="height: 5vh;"><%= articles[i].POST_DATE %></td>
                                    <td style="height: 5vh;"><%= articles[i].AUTHOR %></td>
                                </tr>
                            <% } %>
                        </table >
                    </div>
                    <button type="button" id="new" onclick="location.href='/board/write'">New</button>
                    <center>
                        <nav class="col-8">
                            <% 
                                var offset = 2;
                                var previousBtnEnabled = page_current>1;
                                var nextBtnEnabled = page_current< page_max; 
                            %>
                            <ul class="pagination pagination-sm justify-content-center align-items-center h-100 mb-0">            
                                <li class="page-item <%= previousBtnEnabled?'':'disabled' %>">
                                    <a class="page-link" href="/board?page=<%= page_current-1 %>&limit=<%= limit %>&search=<%= search %>" <%= previousBtnEnabled?'':'tabindex=-1' %>>«</a>
                                </li>
                                <% for(i=1;i<=page_max;i++){ %>
                                    <% if(i==1 || i==page_max || (i>=page_current-offset && i<=page_current+offset)){ %>
                                        <li class="page-item <%= page_current==i?'active':'' %>"><a class="page-link" href="/board?page=<%= i %>&limit=<%= limit %>&search=<%= search %>"> <%= i %> </a></li>
                                    <% } else if(i==2 || i==page_max-1){ %>
                                        <li><a class="page-link">...</a></li>
                                    <% } %>
                                <% } %>
                                <li class="page-item <%= nextBtnEnabled?'':'disabled' %>">
                                    <a class="page-link" href="/board?page=<%= page_current+1 %>&limit=<%= limit %>&search=<%= search %>" <%= nextBtnEnabled?'':'tabindex=-1' %>>»</a>
                                </li>
                            </ul>
                        </nav>
                    </center>
				</ul>
			</ul>
		</div>
        <script>
            function search() {
                const search = document.getElementById('search').value;
                window.location.href = "/board?search="+search+"&page=<%= page_current %>&limit=<%= limit %>";
                // window.location.href = '/board?search='+search;
            }
            function detectKeystroke() {
                const keyStroke = document.getElementById('search').value;
                if(keyStroke==='') {
                    $("#titles").empty();
                    return ;
                }
                $.ajax({
                    type: "get",
                    url: '/board/:keyStroke',
                    data: {keyStroke:keyStroke},
                    dataType:'json',
                    success: function(res) {
                        let temp = '';
                        for(let i=0;i<res.length;i++) {
                            temp += `<option value="${res[i]}" id="title">`;
                        }
                        $('#titles').empty().append(temp);
                    }
                });
            }
            function signOut() {
                $.ajax({
                    type: "delete",
                    url: '/user/logout',
                    data: {},
                    dataType:'text',
                    success: function(res) {
                        location.reload();
                    }
                });
            } 
        </script>
        <style>
            .ribht {
                float: right;
            }
            #new, {
                float: right;
                margin-right: 3%;
            }
            table {
                width: 97%;
            }
            tr {;
                height: 30px;
            }
            table, tr, td, th {
                border: 1px solid;
                text-align: center;
            }
            #new, #searchButton {
                margin-top: 2%;
                width: 19%;
                height: 34px;
                border-radius: 50px;
                background: black;
                color: white;
                display: inline-block;
            }
        </style>
		<!-- Option 1: Bootstrap Bundle with Popper -->
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
	</body>
</html>
  • 본인 정보로 교체하시면 됩니다.
    🔽 .env 🔽
# For user account DB in mongodb
MONGO_URI = mongodb+srv://'YourID':'YourPW'@cluster0.cefr7.mongodb.net/'YourDB'?retryWrites=true&w=majority

# For jsonwebtoken secret key
SECRET_KEY = 'YourjwtSecret';

# For MySQL board DB
SQL_USER = 'YourID'
SQL_PASSWORD = 'YourPW'

🔽 boardWrite.ejs 🔽

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<link rel="stylesheet" type="text/css" href="/public/css/styles.css">
		<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
		<title>Onam Kwon</title>
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;"></body>
		<!-- Bootstrap CSS
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;">
		 -->
	</head>
	
	<body>
		<div class="navbar">
			<a class="active" href="/">Onam Kwon</a> 
			<a href="/dev">Dev</a> 
			<a href="/private">Private</a> 
			<a href="/about">About</a>
		</div>  
		<div class="wrapper">
			<ul class="item-list mySelf">
				<ul class="about">
					<article class="about-text">
                        <div>
                            <h1><%= user.id %>, Welcome! </h1>
                        </div>
                        <div>
                            <form id="board">
                                <div>
                                    <h1 style="display: inline-block;">Board</h1>
                                </div>
                                <center>
                                    <div class="field">
                                        <label for="title"> <h3>Title: </h3></label> <br>
                                        <input type="text" id="title" name="title" placeholder="Enter a title here." style="width: 53%;"/> <br><br>
                                    </div>
                                    <div class="field">
                                        <label for="content"> <h3>Contents: </h3></label> <br>
                                        <textarea id="content" rows="10%" cols="100%" /></textarea>
                                    </div>
                                    <button type="button" id="back" onclick="location.href='/board'">Back</button>
                                    <button type="button" id="post" onclick="postArticle()" >Post</button> <br><br>
                                </center>
                            </form>
                        </div>
                        <div>
                            
                        </div>
                        <script>
                            function postArticle() {
                                const title = document.getElementById("title").value;
                                const content = document.getElementById("content").value;
                                $.ajax({
                                    type: "post",
                                    url: '/board/article',
                                    data: {title: title, content:content},
                                    dataType:'text',
                                    success: function(res) {
                                        alert(res);
                                        window.location.href = '/board';
                                    }
                                });
                            }
                        </script>
                        <style>
                            #back, #post {
                                margin-top: 2%;
                                width: 19%;
                                height: 34px;
                                border-radius: 50px;
                                background: black;
                                color: white;
                                display: inline-block;
                            }
                        </style>
					</article>
				</ul>
			</ul>			
		</div>
		<!-- Option 1: Bootstrap Bundle with Popper -->
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
	</body>
</html>

🔽 login.html 🔽

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<link rel="stylesheet" type="text/css" href="/public/css/styles.css">
		<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
		<title>Onam Kwon</title>
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;"></body>
		<!-- Bootstrap CSS
		<body class="body" style="background-color: #1c1c1e; color: rgb(221,221,221); font-family: Sans-serif; margin:0 auto;">
		 -->
	</head>
	
	<body>
		<div class="navbar">
			<a class="active" href="/">Onam Kwon</a> 
			<a href="/dev">Dev</a> 
			<a href="/private">Private</a> 
			<a href="/about">About</a>
		</div>  
		<div class="wrapper">
			<ul class="item-list mySelf">
				<ul class="about">
					<article class="about-text">
                        <center>
                            <form id="signup">
                                <h1>Sign up</h1>
                                <div class="field">
                                    <label for="id">ID:</label>
                                    <input type="text" id="id" name="id" placeholder="Enter your fullname" /><br><br>
                                </div>
                                <div class="field">
                                    <label for="pw">Address:</label>
                                    <input type="text" id="address" name="address" placeholder="Enter your address" /><br><br>
                                </div>
                                <div class="field">
                                    <label for="pw">PW:</label>
                                    <input type="text" id="pw" name="pw" placeholder="Enter your password" /><br><br>
                                </div>
                                <div class="field">
                                    <label for="pwc">PW confirmation:</label>
                                    <input type="text" id="pwc" name="pwc" placeholder="PW confirmation" /><br><br>
                                </div>
                                <button id="signUp" type="button" onclick="SignUpAjax()">Sign Up</button><br><br>
                            </form>
                        </center>
                        <center>
                            <form id="singin">
                                <h1>Sign in</h1>
                                <div class="field">
                                    <label for="signinID">ID:</label>
                                    <input type="text" id="signinID" name="signinID" placeholder="Enter your fullname" /><br><br>
                                </div>
                                <div class="field">
                                    <label for="signinPW">PW:</label>
                                    <input type="text" id="signinPW" name="signinPW" placeholder="Enter your password" /><br><br>
                                </div>
                                <!-- <button type="text">Log in</button><br><br> -->
                                <button id="signIn" type="button" onclick="signInAjax()">Log in</button><br><br>
                            </form>
                        </center>
					</article>
				</ul>
			</ul>			
		</div>
        <script>
            function SignUpAjax() {
                const id = document.getElementById("id").value;
                const address = document.getElementById("address").value;
                const pw = document.getElementById("pw").value;
                const pwc = document.getElementById("pwc").value;
                document.getElementById("id").value = "";
                document.getElementById("address").value = "";
                document.getElementById("pw").value = "";
                document.getElementById("pwc").value = "";
                $.ajax({
                    type: "post",
                    url: '/user/:id/:address/:pw/:pwc',
                    data: {id:id,address:address,pw:pw,pwc:pwc},
                    dataType:'text',
                    success: function(res) {
                        window.alert(res);
                    }
                });
            }

            function signInAjax() {
                const id = document.getElementById("signinID").value;
                const pw = document.getElementById("signinPW").value;
                document.getElementById("signinID").value = "";
                document.getElementById("signinPW").value = "";
                $.ajax({
                    type: "post",
                    url: '/user/:id/:pw',
                    data: {id:id,pw:pw},
                    dataType:'text',
                    success: function(res) {
                        if(res.length == 0) {
                            location.reload();
                        } else {
                            alert(res);
                        }
                    }
                });
            }

            $("#pwc").on("keyup", function () {
                if(window.event.keyCode==13 && $(this).val()!=""){
                    SignUpAjax();
                }
            });
            $("#signinPW").on("keyup", function () {
                if(window.event.keyCode==13 && $(this).val()!=""){
                    signInAjax();
                }
            });
        </script>
        <style>
            #signIn, #signUp {
                margin-top: 2%;
                width: 19%;
                height: 34px;
                border-radius: 50px;
                background: black;
                color: white;
                display: inline-block;
            }
        </style>
		<!-- Option 1: Bootstrap Bundle with Popper -->
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
	</body>
</html>

코드 설명

클라이언트

  • 로그인은 전에 이미 구현해 놨으므로 그대로 가져온 후 /board경로에서도 사용할 예정
  • 아래 부터는 설명의 편의를 위해 코드의 일부만 가져오므로 코드 복붙은 위에서 해주세요.

🔽 boardWrite.js 🔽

<div>
  <form id="board">
    <center>
      <div class="field">
        <label for="title"> <h3>Title: </h3></label> <br>
        <input type="text" id="title" name="title" placeholder="Enter a title here." style="width: 53%;"/> <br><br>
      </div>
      <div class="field">
        <label for="content"> <h3>Contents: </h3></label> <br>
        <textarea id="content" rows="10%" cols="100%" /></textarea>
      </div>
    <button type="button" id="back" onclick="location.href='/board'">Back</button>
    <button type="button" id="post" onclick="postArticle()" >Post</button> <br><br>
    </center>
  </form>
</div>
<script>
	function postArticle() {
		const title = document.getElementById("title").value;
		const content = document.getElementById("content").value;
		$.ajax({
		type: "post",
		url: '/board/article',
		data: {title: title, content:content},
		dataType:'text',
		success: function(res) {
			alert(res);
			window.location.href = '/board';
		}
		});
	}
</script>
  • title content 부분을 작성하고 post버튼을 누르면 자바스크립트의 postArticle()함수를 호출한다.
    • /board/article라우터로 title content값과 함께 POST요청한다.

NodeJS 서버

🔽 server.js 🔽

const boardRouter = require('./routes/boardRouter');
app.use('/board', boardRouter);
  • 요청은 /board경로의 boardRouter경로로 타고들어간다.

🔽 boardRouter.js 🔽

// Importing controller
const boardMiddleWare = require('../controllers/board/board.controller');
const auth = require("../controllers/authMiddleware");

// path: /board
router.use('/', auth);

// path: /board/article
router.post('/article', boardMiddleWare.insertArticle);

module.exports = router;
  • router.use('/', auth);/board 경로로 들어오는 요청들에게 로그인 토큰을 가지고 있는지 확인할 수 있다.
  • router.post('/article', boardMiddleWare.insertArticle); 에서 실제 경로는 /board/article이지만 server.js파일에서 /board로 라우팅을 해줬기 때문에 /board.article이 아닌 /article만 써줘야 한다.
    • 이 라우터에서 boardMiddleWare에서 가져온 insertArticle함수를 호출해준다.
  • boardMiddleWare.insertArticle()
    🔽 board.controller.js 🔽
const dbMySQLModel = require('../../models/boardDBController');

exports.insertArticle = async (req, res) => {
    const user = req.decoded;
    const { title, content } = req.body;
    if(user) {
        const author = user.id;
        await dbMySQLModel.insert(title, content, author);
        return res.status(200).send('Article has been posted.').end(); 
    } else {
        return res.sendFile(path.join(__dirname, '../../views/board/login.html'));
    }
}
  • boardRouter에서 auth를 이용해 로그인을 확인 한 후
  • 로그인 한 상태라면
    • 유저의 아이디값을 author에 초기화 한다.
    • await dbMySQLModel.insert(title, content, author);를 이용해 body에서 받아온 title content author값을 넣어준다.
    • await를 사용했으므로 위 작업이 완료 된 후 완료 메시지 응답.
  • 로그인 안한 상태라면
    • 로그인 화면 응답.

MySQL DB

  • 🔽 MySQL => board_db DB, BOARD table. 🔽

mysql> use board_db;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show columns from BOARD;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| BOARD_NO    | int      | NO   | PRI | NULL    | auto_increment |
| TITLE       | text     | NO   |     | NULL    |                |
| content     | longtext | NO   |     | NULL    |                |
| POST_DATE   | date     | NO   |     | NULL    |                |
| UPDATE_DATE | date     | NO   |     | NULL    |                |
| AUTHOR      | text     | NO   |     | NULL    |                |
+-------------+----------+------+-----+---------+----------------+
  • 위와같이 게시판 게시글을 저장할 테이블을 만들어 준다.
  • dbMySQLModel.insert()
    🔽 boardDBController.js 🔽
exports.insert = async (title, content, author) => {
    // Query to insert multiple rows
    let query = `INSERT INTO BOARD (TITLE, content, POST_DATE, UPDATE_DATE, AUTHOR) VALUES ?;`;
    
    const date_obj = new Date();
    let post_date = date_obj.getFullYear() +"-"+ parseInt(date_obj.getMonth()+1) +"-"+ date_obj.getDate();
    const update_date = post_date;
    // Values to be inserted
    let values = [
        [title, content, post_date, update_date, author]
    ];
    // Executing the query
    await conn.query(query, [values]);
}
  • 브라우저에서 새로운 게시글을 생성할 때 마다 테이블에 아래처럼 게시글이 저장된다.
mysql> select * from BOARD;
+----------+----------------------------+---------------------------------------+------------+-------------+--------+
| BOARD_NO | TITLE                      | content                               | POST_DATE  | UPDATE_DATE | AUTHOR |
+----------+----------------------------+---------------------------------------+------------+-------------+--------+
|        1 | first                      | first content                         | 2022-08-21 | 2022-08-22  |        |
|        3 | this is a title23          |     this is a content 2  3            | 2022-08-22 | 2022-08-25  | one    |
|        4 | this is a title445         |   this is a content 4 4 5             | 2022-08-22 | 2022-08-25  | one    |
|        5 | this is a title4           | this is a content 4                   | 2022-08-22 | 2022-08-23  | one    |
|        6 | this is a title6           |  this is a content 6                  | 2022-08-22 | 2022-08-25  | one    |
|        7 | this is a title5           | this is a content 5                   | 2022-08-22 | 2022-08-23  | one    |
|       11 | title from user five       | here I go by user five.               | 2022-08-24 | 2022-08-24  | five   |
|       12 | another one from user five | here I go                             | 2022-08-24 | 2022-08-24  | five   |
|       13 | wefewf                     | aassdf                                | 2022-08-24 | 2022-08-24  | five   |
|       15 | helloooo                   | hiiii                                 | 2022-08-24 | 2022-08-24  | five   |
|       18 | this is a titl5555         |   this is a content 5555              | 2022-08-24 | 2022-08-26  | one    |
|       19 | working good? edit good?   |  I think so yeah?
I thinks so yeah?? | 2022-08-25 | 2022-08-25  | one    |
|       20 | ff                         | ke                                    | 2022-08-25 | 2022-08-25  | one    |
|       21 | test                       | test2                                 | 2022-08-25 | 2022-08-25  | one    |
|       22 | 1                          | 1                                     | 2022-08-25 | 2022-08-25  | one    |
|       23 | test                       | done?                                 | 2022-08-26 | 2022-08-26  | one    |
|       24 | Writing a new article      | This is a content                     | 2022-08-29 | 2022-08-29  | one    |
+----------+----------------------------+---------------------------------------+------------+-------------+--------+
17 rows in set (0.00 sec)
  • 클라이언트에서 게시글 확인은 페이지 기능으로 인해 다음 글에 올리도록 하겠습니다.

profile
권오남 / Onam Kwon

0개의 댓글